From 5e5886f2c70ddbcc66bed41269e44efb5e298104 Mon Sep 17 00:00:00 2001 From: Lee <6251863+ltyu@users.noreply.github.com> Date: Thu, 16 May 2024 12:52:51 -0400 Subject: [PATCH 01/73] feat: Create EvmIcaModule (#3729) ### Description Adds an ICA Module with `create()` to be used in an upcoming CoreModule PR. For now the `read()` and `update()` are not implemented because its unclear if they will be used in CoreModule ### Backward compatibility Yes ### Testing Unit Tests --- .changeset/cuddly-trains-rescue.md | 5 ++ .../sdk/src/core/EvmIcaModule.hardhat-test.ts | 44 +++++++++++ typescript/sdk/src/core/EvmIcaModule.ts | 77 +++++++++++++++++++ 3 files changed, 126 insertions(+) create mode 100644 .changeset/cuddly-trains-rescue.md create mode 100644 typescript/sdk/src/core/EvmIcaModule.hardhat-test.ts create mode 100644 typescript/sdk/src/core/EvmIcaModule.ts diff --git a/.changeset/cuddly-trains-rescue.md b/.changeset/cuddly-trains-rescue.md new file mode 100644 index 000000000..11ed54df7 --- /dev/null +++ b/.changeset/cuddly-trains-rescue.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/sdk': minor +--- + +Add EvmIcaModule.create() diff --git a/typescript/sdk/src/core/EvmIcaModule.hardhat-test.ts b/typescript/sdk/src/core/EvmIcaModule.hardhat-test.ts new file mode 100644 index 000000000..cbbb9cbe0 --- /dev/null +++ b/typescript/sdk/src/core/EvmIcaModule.hardhat-test.ts @@ -0,0 +1,44 @@ +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers.js'; +import { expect } from 'chai'; +import { ethers } from 'ethers'; +import hre from 'hardhat'; + +import { Mailbox, Mailbox__factory } from '@hyperlane-xyz/core'; + +import { TestChainName } from '../consts/testChains.js'; +import { MultiProvider } from '../providers/MultiProvider.js'; + +import { EvmIcaModule } from './EvmIcaModule.js'; + +describe('EvmIcaModule', async () => { + const LOCAL_DOMAIN = 1; + let signer: SignerWithAddress; + let multiProvider: MultiProvider; + let mailbox: Mailbox; + + before(async () => { + [signer] = await hre.ethers.getSigners(); + multiProvider = MultiProvider.createTestMultiProvider({ signer }); + const Mailbox = new Mailbox__factory(signer); + mailbox = await Mailbox.deploy(LOCAL_DOMAIN); + }); + describe('Create', async () => { + it('should deploy an ICA with ISM', async () => { + const evmIcaModule = await EvmIcaModule.create({ + chain: TestChainName.test1, + config: { + mailbox: mailbox.address, + owner: signer.address, + }, + multiProvider, + }); + + const { interchainAccountRouter, interchainAccountIsm } = + evmIcaModule.serialize(); + expect(interchainAccountIsm).to.not.equal(ethers.constants.AddressZero); + expect(interchainAccountRouter).to.not.equal( + ethers.constants.AddressZero, + ); + }); + }); +}); diff --git a/typescript/sdk/src/core/EvmIcaModule.ts b/typescript/sdk/src/core/EvmIcaModule.ts new file mode 100644 index 000000000..47bff2ede --- /dev/null +++ b/typescript/sdk/src/core/EvmIcaModule.ts @@ -0,0 +1,77 @@ +import { ProtocolType, rootLogger } from '@hyperlane-xyz/utils'; + +import { serializeContracts } from '../contracts/contracts.js'; +import { HyperlaneAddresses } from '../contracts/types.js'; +import { InterchainAccountDeployer } from '../middleware/account/InterchainAccountDeployer.js'; +import { InterchainAccountFactories } from '../middleware/account/contracts.js'; +import { MultiProvider } from '../providers/MultiProvider.js'; +import { EthersV5Transaction } from '../providers/ProviderType.js'; +import { ProxiedRouterConfig } from '../router/types.js'; +import { ChainNameOrId } from '../types.js'; + +import { + HyperlaneModule, + HyperlaneModuleArgs, +} from './AbstractHyperlaneModule.js'; + +export type InterchainAccountConfig = ProxiedRouterConfig; + +export class EvmIcaModule extends HyperlaneModule< + ProtocolType.Ethereum, + InterchainAccountConfig, + HyperlaneAddresses +> { + protected logger = rootLogger.child({ module: 'EvmIcaModule' }); + + protected constructor( + protected readonly multiProvider: MultiProvider, + args: HyperlaneModuleArgs< + InterchainAccountConfig, + HyperlaneAddresses + >, + ) { + super(args); + } + + public async read(): Promise { + throw new Error('Method not implemented.'); + } + + public async update( + _config: InterchainAccountConfig, + ): Promise { + throw new Error('Method not implemented.'); + } + + /** + * Creates a new EvmIcaModule instance by deploying an ICA with an ICA ISM. + * + * @param chain - The chain on which to deploy the ICA. + * @param config - The configuration for the ICA. + * @param multiProvider - The MultiProvider instance to use for deployment. + * @returns {Promise} - A new EvmIcaModule instance. + */ + public static async create({ + chain, + config, + multiProvider, + }: { + chain: ChainNameOrId; + config: InterchainAccountConfig; + multiProvider: MultiProvider; + }): Promise { + const interchainAccountDeployer = new InterchainAccountDeployer( + multiProvider, + ); + const deployedContracts = await interchainAccountDeployer.deployContracts( + multiProvider.getChainName(chain), + config, + ); + + return new EvmIcaModule(multiProvider, { + addresses: serializeContracts(deployedContracts), + chain, + config, + }); + } +} From 92bcb6f1f077fd885a74a29364fd3802bbf0b7ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Noah=20Bayindirli=20=F0=9F=A5=82?= Date: Fri, 17 May 2024 20:46:54 -0400 Subject: [PATCH 02/73] chore: Merge branch 'main' into noah/cli-2.0 (#3809) ### Description ### Drive-by changes ### Related issues ### Backward compatibility ### Testing --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: Kunal Arora <55632507+aroralanuk@users.noreply.github.com> Co-authored-by: Connor McEwen --- .changeset/green-ads-live.md | 5 -- .changeset/lemon-horses-swim.md | 5 -- .changeset/olive-apricots-develop.md | 5 -- .changeset/quiet-cheetahs-own.md | 5 -- .changeset/sixty-avocados-double.md | 5 -- .changeset/sour-bats-sort.md | 6 --- .github/workflows/lint-pr.yaml | 20 ++++++++ solidity/CHANGELOG.md | 7 +++ solidity/package.json | 4 +- typescript/ccip-server/CHANGELOG.md | 2 + typescript/ccip-server/package.json | 74 ++++++++++++++-------------- typescript/cli/CHANGELOG.md | 16 ++++++ typescript/cli/package.json | 6 +-- typescript/cli/src/version.ts | 2 +- typescript/helloworld/CHANGELOG.md | 9 ++++ typescript/helloworld/package.json | 6 +-- typescript/infra/CHANGELOG.md | 10 ++++ typescript/infra/package.json | 8 +-- typescript/sdk/CHANGELOG.md | 13 +++++ typescript/sdk/package.json | 6 +-- typescript/utils/CHANGELOG.md | 6 +++ typescript/utils/package.json | 2 +- yarn.lock | 28 +++++------ 23 files changed, 151 insertions(+), 99 deletions(-) delete mode 100644 .changeset/green-ads-live.md delete mode 100644 .changeset/lemon-horses-swim.md delete mode 100644 .changeset/olive-apricots-develop.md delete mode 100644 .changeset/quiet-cheetahs-own.md delete mode 100644 .changeset/sixty-avocados-double.md delete mode 100644 .changeset/sour-bats-sort.md create mode 100644 .github/workflows/lint-pr.yaml diff --git a/.changeset/green-ads-live.md b/.changeset/green-ads-live.md deleted file mode 100644 index f847f584b..000000000 --- a/.changeset/green-ads-live.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperlane-xyz/cli': minor ---- - -Default to home directory for local registry diff --git a/.changeset/lemon-horses-swim.md b/.changeset/lemon-horses-swim.md deleted file mode 100644 index fe5b1c953..000000000 --- a/.changeset/lemon-horses-swim.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperlane-xyz/cli': patch ---- - -Improve defaults in chain config command diff --git a/.changeset/olive-apricots-develop.md b/.changeset/olive-apricots-develop.md deleted file mode 100644 index b1523f71d..000000000 --- a/.changeset/olive-apricots-develop.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperlane-xyz/cli': minor ---- - -Allows a developer to pass a private key or address to dry-run, and ensures HYP_KEY is only used for private keys. diff --git a/.changeset/quiet-cheetahs-own.md b/.changeset/quiet-cheetahs-own.md deleted file mode 100644 index df8957059..000000000 --- a/.changeset/quiet-cheetahs-own.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperlane-xyz/sdk': patch ---- - -Exports submitter and transformer props types. diff --git a/.changeset/sixty-avocados-double.md b/.changeset/sixty-avocados-double.md deleted file mode 100644 index 821e105c0..000000000 --- a/.changeset/sixty-avocados-double.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperlane-xyz/cli': minor ---- - -Add CLI-side submitter to use SDK submitter from CRUD and other command modules. diff --git a/.changeset/sour-bats-sort.md b/.changeset/sour-bats-sort.md deleted file mode 100644 index 37d54bb10..000000000 --- a/.changeset/sour-bats-sort.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@hyperlane-xyz/utils': minor -'@hyperlane-xyz/sdk': minor ---- - -Implement aggregation and multisig ISM metadata encoding diff --git a/.github/workflows/lint-pr.yaml b/.github/workflows/lint-pr.yaml new file mode 100644 index 000000000..f530f652f --- /dev/null +++ b/.github/workflows/lint-pr.yaml @@ -0,0 +1,20 @@ +name: 'Lint PR' + +on: + pull_request_target: + types: + - opened + - edited + - synchronize + +permissions: + pull-requests: read + +jobs: + main: + name: Validate PR title + runs-on: ubuntu-latest + steps: + - uses: amannn/action-semantic-pull-request@cfb60706e18bc85e8aec535e3c577abe8f70378e + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/solidity/CHANGELOG.md b/solidity/CHANGELOG.md index bb3548828..e1735ad8e 100644 --- a/solidity/CHANGELOG.md +++ b/solidity/CHANGELOG.md @@ -1,5 +1,12 @@ # @hyperlane-xyz/core +## 3.12.0 + +### Patch Changes + +- Updated dependencies [69de68a66] + - @hyperlane-xyz/utils@3.12.0 + ## 3.11.1 ### Patch Changes diff --git a/solidity/package.json b/solidity/package.json index 96077c433..3115dab14 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.11.1", + "version": "3.12.2", "dependencies": { "@eth-optimism/contracts": "^0.6.0", - "@hyperlane-xyz/utils": "3.11.1", + "@hyperlane-xyz/utils": "3.12.2", "@layerzerolabs/lz-evm-oapp-v2": "2.0.2", "@openzeppelin/contracts": "^4.9.3", "@openzeppelin/contracts-upgradeable": "^v4.9.3", diff --git a/typescript/ccip-server/CHANGELOG.md b/typescript/ccip-server/CHANGELOG.md index 561ec8845..bcee12745 100644 --- a/typescript/ccip-server/CHANGELOG.md +++ b/typescript/ccip-server/CHANGELOG.md @@ -1,5 +1,7 @@ # @hyperlane-xyz/ccip-server +## 3.12.0 + ## 3.11.1 ## 3.11.0 diff --git a/typescript/ccip-server/package.json b/typescript/ccip-server/package.json index 1fe18f6bc..f7a9829c8 100644 --- a/typescript/ccip-server/package.json +++ b/typescript/ccip-server/package.json @@ -1,39 +1,39 @@ { - "name": "@hyperlane-xyz/ccip-server", - "version": "3.11.1", - "description": "CCIP server", - "typings": "dist/index.d.ts", - "typedocMain": "src/index.ts", - "private": true, - "files": [ - "src" - ], - "engines": { - "node": ">=16" - }, - "scripts": { - "start": "tsx src/server.ts", - "dev": "nodemon src/server.ts", - "test": "jest", - "prettier": "prettier --write ./src/* ./tests/" - }, - "author": "brolee", - "license": "Apache-2.0", - "devDependencies": { - "@jest/globals": "^29.7.0", - "@types/node": "^16.9.1", - "jest": "^29.7.0", - "nodemon": "^3.0.3", - "prettier": "^2.8.8", - "ts-jest": "^29.1.2", - "ts-node": "^10.8.0", - "tsx": "^4.7.1", - "typescript": "5.3.3" - }, - "dependencies": { - "@chainlink/ccip-read-server": "^0.2.1", - "dotenv-flow": "^4.1.0", - "ethers": "5.7.2", - "hyperlane-explorer": "https://github.com/hyperlane-xyz/hyperlane-explorer.git" - } + "name": "@hyperlane-xyz/ccip-server", + "version": "3.12.2", + "description": "CCIP server", + "typings": "dist/index.d.ts", + "typedocMain": "src/index.ts", + "private": true, + "files": [ + "src" + ], + "engines": { + "node": ">=16" + }, + "scripts": { + "start": "tsx src/server.ts", + "dev": "nodemon src/server.ts", + "test": "jest", + "prettier": "prettier --write ./src/* ./tests/" + }, + "author": "brolee", + "license": "Apache-2.0", + "devDependencies": { + "@jest/globals": "^29.7.0", + "@types/node": "^16.9.1", + "jest": "^29.7.0", + "nodemon": "^3.0.3", + "prettier": "^2.8.8", + "ts-jest": "^29.1.2", + "ts-node": "^10.8.0", + "tsx": "^4.7.1", + "typescript": "5.3.3" + }, + "dependencies": { + "@chainlink/ccip-read-server": "^0.2.1", + "dotenv-flow": "^4.1.0", + "ethers": "5.7.2", + "hyperlane-explorer": "https://github.com/hyperlane-xyz/hyperlane-explorer.git" + } } diff --git a/typescript/cli/CHANGELOG.md b/typescript/cli/CHANGELOG.md index 118f93943..8f42fe354 100644 --- a/typescript/cli/CHANGELOG.md +++ b/typescript/cli/CHANGELOG.md @@ -1,5 +1,21 @@ # @hyperlane-xyz/cli +## 3.12.0 + +### Minor Changes + +- cc8731985: Default to home directory for local registry +- ff221f66a: Allows a developer to pass a private key or address to dry-run, and ensures HYP_KEY is only used for private keys. +- eba393680: Add CLI-side submitter to use SDK submitter from CRUD and other command modules. + +### Patch Changes + +- 2b7dfe27e: Improve defaults in chain config command +- Updated dependencies [eba393680] +- Updated dependencies [69de68a66] + - @hyperlane-xyz/sdk@3.12.0 + - @hyperlane-xyz/utils@3.12.0 + ## 3.11.1 ### Patch Changes diff --git a/typescript/cli/package.json b/typescript/cli/package.json index b5ce7a6f1..4d5ddf2db 100644 --- a/typescript/cli/package.json +++ b/typescript/cli/package.json @@ -1,11 +1,11 @@ { "name": "@hyperlane-xyz/cli", - "version": "3.11.1", + "version": "3.12.2", "description": "A command-line utility for common Hyperlane operations", "dependencies": { "@hyperlane-xyz/registry": "^1.0.7", - "@hyperlane-xyz/sdk": "3.11.1", - "@hyperlane-xyz/utils": "3.11.1", + "@hyperlane-xyz/sdk": "3.12.2", + "@hyperlane-xyz/utils": "3.12.2", "@inquirer/prompts": "^3.0.0", "bignumber.js": "^9.1.1", "chalk": "^5.3.0", diff --git a/typescript/cli/src/version.ts b/typescript/cli/src/version.ts index 68e87af9a..f1447b455 100644 --- a/typescript/cli/src/version.ts +++ b/typescript/cli/src/version.ts @@ -1 +1 @@ -export const VERSION = '3.11.1'; +export const VERSION = '3.12.2'; diff --git a/typescript/helloworld/CHANGELOG.md b/typescript/helloworld/CHANGELOG.md index fec151087..979fb8285 100644 --- a/typescript/helloworld/CHANGELOG.md +++ b/typescript/helloworld/CHANGELOG.md @@ -1,5 +1,14 @@ # @hyperlane-xyz/helloworld +## 3.12.0 + +### Patch Changes + +- Updated dependencies [eba393680] +- Updated dependencies [69de68a66] + - @hyperlane-xyz/sdk@3.12.0 + - @hyperlane-xyz/core@3.12.0 + ## 3.11.1 ### Patch Changes diff --git a/typescript/helloworld/package.json b/typescript/helloworld/package.json index edd483b2f..f577cf8d8 100644 --- a/typescript/helloworld/package.json +++ b/typescript/helloworld/package.json @@ -1,11 +1,11 @@ { "name": "@hyperlane-xyz/helloworld", "description": "A basic skeleton of an Hyperlane app", - "version": "3.11.1", + "version": "3.12.2", "dependencies": { - "@hyperlane-xyz/core": "3.11.1", + "@hyperlane-xyz/core": "3.12.2", "@hyperlane-xyz/registry": "^1.0.7", - "@hyperlane-xyz/sdk": "3.11.1", + "@hyperlane-xyz/sdk": "3.12.2", "@openzeppelin/contracts-upgradeable": "^4.9.3", "ethers": "^5.7.2" }, diff --git a/typescript/infra/CHANGELOG.md b/typescript/infra/CHANGELOG.md index 4c249b07d..21b608413 100644 --- a/typescript/infra/CHANGELOG.md +++ b/typescript/infra/CHANGELOG.md @@ -1,5 +1,15 @@ # @hyperlane-xyz/infra +## 3.12.0 + +### Patch Changes + +- Updated dependencies [eba393680] +- Updated dependencies [69de68a66] + - @hyperlane-xyz/sdk@3.12.0 + - @hyperlane-xyz/utils@3.12.0 + - @hyperlane-xyz/helloworld@3.12.0 + ## 3.11.1 ### Patch Changes diff --git a/typescript/infra/package.json b/typescript/infra/package.json index a000f25e1..428569c33 100644 --- a/typescript/infra/package.json +++ b/typescript/infra/package.json @@ -1,7 +1,7 @@ { "name": "@hyperlane-xyz/infra", "description": "Infrastructure utilities for the Hyperlane Network", - "version": "3.11.1", + "version": "3.12.2", "dependencies": { "@arbitrum/sdk": "^3.0.0", "@aws-sdk/client-iam": "^3.74.0", @@ -12,10 +12,10 @@ "@ethersproject/experimental": "^5.7.0", "@ethersproject/hardware-wallets": "^5.7.0", "@ethersproject/providers": "^5.7.2", - "@hyperlane-xyz/helloworld": "3.11.1", + "@hyperlane-xyz/helloworld": "3.12.2", "@hyperlane-xyz/registry": "^1.0.7", - "@hyperlane-xyz/sdk": "3.11.1", - "@hyperlane-xyz/utils": "3.11.1", + "@hyperlane-xyz/sdk": "3.12.2", + "@hyperlane-xyz/utils": "3.12.2", "@nomiclabs/hardhat-etherscan": "^3.0.3", "@solana/web3.js": "^1.78.0", "asn1.js": "5.4.1", diff --git a/typescript/sdk/CHANGELOG.md b/typescript/sdk/CHANGELOG.md index f87c40305..152e40bd6 100644 --- a/typescript/sdk/CHANGELOG.md +++ b/typescript/sdk/CHANGELOG.md @@ -1,5 +1,18 @@ # @hyperlane-xyz/sdk +## 3.12.0 + +### Minor Changes + +- 69de68a66: Implement aggregation and multisig ISM metadata encoding + +### Patch Changes + +- eba393680: Exports submitter and transformer props types. +- Updated dependencies [69de68a66] + - @hyperlane-xyz/utils@3.12.0 + - @hyperlane-xyz/core@3.12.0 + ## 3.11.1 ### Patch Changes diff --git a/typescript/sdk/package.json b/typescript/sdk/package.json index d543d5856..c0fa21462 100644 --- a/typescript/sdk/package.json +++ b/typescript/sdk/package.json @@ -1,12 +1,12 @@ { "name": "@hyperlane-xyz/sdk", "description": "The official SDK for the Hyperlane Network", - "version": "3.11.1", + "version": "3.12.2", "dependencies": { "@cosmjs/cosmwasm-stargate": "^0.31.3", "@cosmjs/stargate": "^0.31.3", - "@hyperlane-xyz/core": "3.11.1", - "@hyperlane-xyz/utils": "3.11.1", + "@hyperlane-xyz/core": "3.12.2", + "@hyperlane-xyz/utils": "3.12.2", "@safe-global/api-kit": "1.3.0", "@safe-global/protocol-kit": "1.3.0", "@solana/spl-token": "^0.3.8", diff --git a/typescript/utils/CHANGELOG.md b/typescript/utils/CHANGELOG.md index 2f6ec6c57..f72911518 100644 --- a/typescript/utils/CHANGELOG.md +++ b/typescript/utils/CHANGELOG.md @@ -1,5 +1,11 @@ # @hyperlane-xyz/utils +## 3.12.0 + +### Minor Changes + +- 69de68a66: Implement aggregation and multisig ISM metadata encoding + ## 3.11.1 ## 3.11.0 diff --git a/typescript/utils/package.json b/typescript/utils/package.json index 02c8cd29f..094b5b053 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.11.1", + "version": "3.12.2", "dependencies": { "@cosmjs/encoding": "^0.31.3", "@solana/web3.js": "^1.78.0", diff --git a/yarn.lock b/yarn.lock index ef6de3e36..2adc97823 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4955,8 +4955,8 @@ __metadata: resolution: "@hyperlane-xyz/cli@workspace:typescript/cli" dependencies: "@hyperlane-xyz/registry": "npm:^1.0.7" - "@hyperlane-xyz/sdk": "npm:3.11.1" - "@hyperlane-xyz/utils": "npm:3.11.1" + "@hyperlane-xyz/sdk": "npm:3.12.2" + "@hyperlane-xyz/utils": "npm:3.12.2" "@inquirer/prompts": "npm:^3.0.0" "@types/mocha": "npm:^10.0.1" "@types/node": "npm:^18.14.5" @@ -4983,12 +4983,12 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/core@npm:3.11.1, @hyperlane-xyz/core@workspace:solidity": +"@hyperlane-xyz/core@npm:3.12.2, @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.11.1" + "@hyperlane-xyz/utils": "npm:3.12.2" "@layerzerolabs/lz-evm-oapp-v2": "npm:2.0.2" "@layerzerolabs/solidity-examples": "npm:^1.1.0" "@nomiclabs/hardhat-ethers": "npm:^2.2.3" @@ -5036,13 +5036,13 @@ __metadata: languageName: node linkType: hard -"@hyperlane-xyz/helloworld@npm:3.11.1, @hyperlane-xyz/helloworld@workspace:typescript/helloworld": +"@hyperlane-xyz/helloworld@npm:3.12.2, @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.11.1" + "@hyperlane-xyz/core": "npm:3.12.2" "@hyperlane-xyz/registry": "npm:^1.0.7" - "@hyperlane-xyz/sdk": "npm:3.11.1" + "@hyperlane-xyz/sdk": "npm:3.12.2" "@nomiclabs/hardhat-ethers": "npm:^2.2.3" "@nomiclabs/hardhat-waffle": "npm:^2.0.6" "@openzeppelin/contracts-upgradeable": "npm:^4.9.3" @@ -5087,10 +5087,10 @@ __metadata: "@ethersproject/experimental": "npm:^5.7.0" "@ethersproject/hardware-wallets": "npm:^5.7.0" "@ethersproject/providers": "npm:^5.7.2" - "@hyperlane-xyz/helloworld": "npm:3.11.1" + "@hyperlane-xyz/helloworld": "npm:3.12.2" "@hyperlane-xyz/registry": "npm:^1.0.7" - "@hyperlane-xyz/sdk": "npm:3.11.1" - "@hyperlane-xyz/utils": "npm:3.11.1" + "@hyperlane-xyz/sdk": "npm:3.12.2" + "@hyperlane-xyz/utils": "npm:3.12.2" "@nomiclabs/hardhat-ethers": "npm:^2.2.3" "@nomiclabs/hardhat-etherscan": "npm:^3.0.3" "@nomiclabs/hardhat-waffle": "npm:^2.0.6" @@ -5150,14 +5150,14 @@ __metadata: languageName: node linkType: hard -"@hyperlane-xyz/sdk@npm:3.11.1, @hyperlane-xyz/sdk@workspace:typescript/sdk": +"@hyperlane-xyz/sdk@npm:3.12.2, @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.11.1" - "@hyperlane-xyz/utils": "npm:3.11.1" + "@hyperlane-xyz/core": "npm:3.12.2" + "@hyperlane-xyz/utils": "npm:3.12.2" "@nomiclabs/hardhat-ethers": "npm:^2.2.3" "@nomiclabs/hardhat-waffle": "npm:^2.0.6" "@safe-global/api-kit": "npm:1.3.0" @@ -5225,7 +5225,7 @@ __metadata: languageName: node linkType: hard -"@hyperlane-xyz/utils@npm:3.11.1, @hyperlane-xyz/utils@workspace:typescript/utils": +"@hyperlane-xyz/utils@npm:3.12.2, @hyperlane-xyz/utils@workspace:typescript/utils": version: 0.0.0-use.local resolution: "@hyperlane-xyz/utils@workspace:typescript/utils" dependencies: From 3dabcbdca01b847cdf6774a192f865800af39a82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Noah=20Bayindirli=20=F0=9F=A5=82?= Date: Mon, 20 May 2024 11:48:56 -0400 Subject: [PATCH 03/73] chore(sdk): add zod validation on submitter/transformers popTx + refactor ICA interface (#3817) See comments and approvals here: https://github.com/hyperlane-xyz/hyperlane-monorepo/pull/3792 --- .changeset/selfish-carpets-do.md | 5 ++ .../infra/src/govern/HyperlaneAppGovernor.ts | 12 ++-- .../middleware/account/InterchainAccount.ts | 63 +++++++++++------ .../account/accounts.hardhat-test.ts | 7 +- .../sdk/src/providers/transactions/schemas.ts | 7 ++ .../ethersV5/EV5GnosisSafeTxSubmitter.ts | 5 +- .../EV5ImpersonatedAccountTxSubmitter.ts | 9 ++- .../submitter/ethersV5/EV5TxSubmitterTypes.ts | 1 - .../EV5InterchainAccountTxTransformer.ts | 70 ++++++++++++------- .../ethersV5/EV5TxTransformerTypes.ts | 12 +--- .../sdk/src/providers/transactions/types.ts | 7 ++ 11 files changed, 129 insertions(+), 69 deletions(-) create mode 100644 .changeset/selfish-carpets-do.md create mode 100644 typescript/sdk/src/providers/transactions/schemas.ts create mode 100644 typescript/sdk/src/providers/transactions/types.ts diff --git a/.changeset/selfish-carpets-do.md b/.changeset/selfish-carpets-do.md new file mode 100644 index 000000000..a410aec2a --- /dev/null +++ b/.changeset/selfish-carpets-do.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/sdk': minor +--- + +Adds zod validation on tx submitter populated txs & re-uses ICA params for transformer. diff --git a/typescript/infra/src/govern/HyperlaneAppGovernor.ts b/typescript/infra/src/govern/HyperlaneAppGovernor.ts index 31eb1dc38..b48bc649f 100644 --- a/typescript/infra/src/govern/HyperlaneAppGovernor.ts +++ b/typescript/infra/src/govern/HyperlaneAppGovernor.ts @@ -198,12 +198,12 @@ export abstract class HyperlaneAppGovernor< accountConfig.owner, )} on ${origin}`, ); - const callRemote = await this.interchainAccount.getCallRemote( - origin, - chain, - [call], - accountConfig, - ); + const callRemote = await this.interchainAccount.getCallRemote({ + chain: origin, + destination: chain, + innerCalls: [call], + config: accountConfig, + }); if (!callRemote.to || !callRemote.data) { return SubmissionType.MANUAL; } diff --git a/typescript/sdk/src/middleware/account/InterchainAccount.ts b/typescript/sdk/src/middleware/account/InterchainAccount.ts index 8d13e8f84..f1c559ad5 100644 --- a/typescript/sdk/src/middleware/account/InterchainAccount.ts +++ b/typescript/sdk/src/middleware/account/InterchainAccount.ts @@ -24,6 +24,14 @@ import { } from './contracts.js'; import { AccountConfig } from './types.js'; +export interface GetCallRemoteSettings { + chain: ChainName; + destination: ChainName; + innerCalls: CallData[]; + config: AccountConfig; + hookMetadata?: BytesLike; +} + export class InterchainAccount extends RouterApp { constructor( contractsMap: HyperlaneContractsMap, @@ -88,13 +96,13 @@ export class InterchainAccount extends RouterApp { } // meant for ICA governance to return the populatedTx - async getCallRemote( - chain: ChainName, - destination: ChainName, - innerCalls: CallData[], - config: AccountConfig, - hookMetadata?: BytesLike, - ): Promise { + async getCallRemote({ + chain, + destination, + innerCalls, + config, + hookMetadata, + }: GetCallRemoteSettings): Promise { const localRouter = this.router(this.contractsMap[chain]); const remoteDomain = this.multiProvider.getDomainId(destination); const quote = await localRouter['quoteGasPayment(uint32)'](remoteDomain); @@ -139,34 +147,49 @@ export class InterchainAccount extends RouterApp { // general helper for different overloaded callRemote functions // can override the gasLimit by StandardHookMetadata.overrideGasLimit for optional hookMetadata here - async callRemote( - chain: ChainName, - destination: ChainName, - calls: Array, - config: AccountConfig, - hookMetadata?: string, - ): Promise { + async callRemote({ + chain, + destination, + innerCalls, + config, + hookMetadata, + }: GetCallRemoteSettings): Promise { await this.multiProvider.sendTransaction( chain, - this.getCallRemote(chain, destination, calls, config, hookMetadata), + this.getCallRemote({ + chain, + destination, + innerCalls, + config, + hookMetadata, + }), ); } } -export async function deployInterchainAccount( +export function buildInterchainAccountApp( multiProvider: MultiProvider, chain: ChainName, config: AccountConfig, -): Promise
{ +): InterchainAccount { if (!config.localRouter) { throw new Error('localRouter is required for account deployment'); } const addressesMap: HyperlaneAddressesMap = { [chain]: { interchainAccountRouter: config.localRouter }, }; - const router = InterchainAccount.fromAddressesMap( - addressesMap, + return InterchainAccount.fromAddressesMap(addressesMap, multiProvider); +} + +export async function deployInterchainAccount( + multiProvider: MultiProvider, + chain: ChainName, + config: AccountConfig, +): Promise
{ + const interchainAccountApp: InterchainAccount = buildInterchainAccountApp( multiProvider, + chain, + config, ); - return router.deployAccount(chain, config); + return interchainAccountApp.deployAccount(chain, config); } diff --git a/typescript/sdk/src/middleware/account/accounts.hardhat-test.ts b/typescript/sdk/src/middleware/account/accounts.hardhat-test.ts index dbf3dbc6b..323f9af53 100644 --- a/typescript/sdk/src/middleware/account/accounts.hardhat-test.ts +++ b/typescript/sdk/src/middleware/account/accounts.hardhat-test.ts @@ -95,7 +95,12 @@ describe('InterchainAccounts', async () => { owner: signer.address, localRouter: local.address, }; - await app.callRemote(localChain, remoteChain, [call], config); + await app.callRemote({ + chain: localChain, + destination: remoteChain, + innerCalls: [call], + config, + }); const balanceAfter = await signer.getBalance(); await coreApp.processMessages(); expect(balanceAfter).to.lte(balanceBefore.sub(quote)); diff --git a/typescript/sdk/src/providers/transactions/schemas.ts b/typescript/sdk/src/providers/transactions/schemas.ts new file mode 100644 index 000000000..da03743ea --- /dev/null +++ b/typescript/sdk/src/providers/transactions/schemas.ts @@ -0,0 +1,7 @@ +import { z } from 'zod'; + +export const PopulatedTransactionSchema = z.object({ + to: z.string(), + data: z.string(), + chainId: z.number(), +}); diff --git a/typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5GnosisSafeTxSubmitter.ts b/typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5GnosisSafeTxSubmitter.ts index 5fb760e32..e04622a33 100644 --- a/typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5GnosisSafeTxSubmitter.ts +++ b/typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5GnosisSafeTxSubmitter.ts @@ -1,4 +1,3 @@ -import { PopulatedTransaction } from 'ethers'; import { Logger } from 'pino'; import { Address, assert, rootLogger } from '@hyperlane-xyz/utils'; @@ -6,6 +5,7 @@ import { Address, assert, rootLogger } from '@hyperlane-xyz/utils'; // @ts-ignore import { getSafe, getSafeService } from '../../../../utils/gnosisSafe.js'; import { MultiProvider } from '../../../MultiProvider.js'; +import { PopulatedTransaction } from '../../types.js'; import { TxSubmitterType } from '../TxSubmitterTypes.js'; import { EV5TxSubmitterInterface } from './EV5TxSubmitterInterface.js'; @@ -39,9 +39,6 @@ export class EV5GnosisSafeTxSubmitter implements EV5TxSubmitterInterface { ); const safeTransactionBatch: any[] = txs.map( ({ to, data, value, chainId }: PopulatedTransaction) => { - assert(to, 'Invalid PopulatedTransaction: Missing to field'); - assert(data, 'Invalid PopulatedTransaction: Missing data field'); - assert(chainId, 'Invalid PopulatedTransaction: Missing chainId field'); const txChain = this.multiProvider.getChainName(chainId); assert( txChain === this.props.chain, diff --git a/typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5ImpersonatedAccountTxSubmitter.ts b/typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5ImpersonatedAccountTxSubmitter.ts index 816baf053..98ca608b1 100644 --- a/typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5ImpersonatedAccountTxSubmitter.ts +++ b/typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5ImpersonatedAccountTxSubmitter.ts @@ -4,7 +4,10 @@ import { Logger } from 'pino'; import { rootLogger } from '@hyperlane-xyz/utils'; -import { impersonateAccount } from '../../../../utils/fork.js'; +import { + impersonateAccount, + stopImpersonatingAccount, +} from '../../../../utils/fork.js'; import { MultiProvider } from '../../../MultiProvider.js'; import { TxSubmitterType } from '../TxSubmitterTypes.js'; @@ -33,6 +36,8 @@ export class EV5ImpersonatedAccountTxSubmitter extends EV5JsonRpcTxSubmitter { this.props.userAddress, ); super.multiProvider.setSharedSigner(impersonatedAccount); - return await super.submit(...txs); + const transactionReceipts = await super.submit(...txs); + await stopImpersonatingAccount(this.props.userAddress); + return transactionReceipts; } } diff --git a/typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5TxSubmitterTypes.ts b/typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5TxSubmitterTypes.ts index cf6f7f164..b09deb1a6 100644 --- a/typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5TxSubmitterTypes.ts +++ b/typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5TxSubmitterTypes.ts @@ -8,6 +8,5 @@ export interface EV5GnosisSafeTxSubmitterProps { } export interface EV5ImpersonatedAccountTxSubmitterProps { - chain: ChainName; userAddress: Address; } diff --git a/typescript/sdk/src/providers/transactions/transformer/ethersV5/EV5InterchainAccountTxTransformer.ts b/typescript/sdk/src/providers/transactions/transformer/ethersV5/EV5InterchainAccountTxTransformer.ts index 25ae331e7..59dcc8edc 100644 --- a/typescript/sdk/src/providers/transactions/transformer/ethersV5/EV5InterchainAccountTxTransformer.ts +++ b/typescript/sdk/src/providers/transactions/transformer/ethersV5/EV5InterchainAccountTxTransformer.ts @@ -1,10 +1,15 @@ -import { PopulatedTransaction } from 'ethers'; +import { ethers } from 'ethers'; import { Logger } from 'pino'; -import { CallData, assert, rootLogger } from '@hyperlane-xyz/utils'; +import { CallData, assert, objKeys, rootLogger } from '@hyperlane-xyz/utils'; +import { + InterchainAccount, + buildInterchainAccountApp, +} from '../../../../middleware/account/InterchainAccount.js'; import { ChainName } from '../../../../types.js'; import { MultiProvider } from '../../../MultiProvider.js'; +import { PopulatedTransaction } from '../../types.js'; import { TxTransformerType } from '../TxTransformerTypes.js'; import { EV5TxTransformerInterface } from './EV5TxTransformerInterface.js'; @@ -21,35 +26,48 @@ export class EV5InterchainAccountTxTransformer constructor( public readonly multiProvider: MultiProvider, public readonly props: EV5InterchainAccountTxTransformerProps, - ) {} + ) { + assert( + this.props.config.localRouter, + 'Invalid AccountConfig: Cannot retrieve InterchainAccount.', + ); + } public async transform( ...txs: PopulatedTransaction[] - ): Promise { - const txChainsToInnerCalls: Record = {}; - - txs.map(({ to, data, value, chainId }: PopulatedTransaction) => { - assert(to, 'Invalid PopulatedTransaction: Missing to field'); - assert(data, 'Invalid PopulatedTransaction: Missing data field'); - assert(chainId, 'Invalid PopulatedTransaction: Missing chainId field'); - const txChain = this.multiProvider.getChainName(chainId); - if (!txChainsToInnerCalls[txChain]) txChainsToInnerCalls[txChain] = []; - txChainsToInnerCalls[txChain].push({ to, data, value }); - }); - - const transformedTxs: Promise[] = []; - Object.keys(txChainsToInnerCalls).map((txChain: ChainName) => { + ): Promise { + const txChainsToInnerCalls: Record = txs.reduce( + ( + txChainToInnerCalls: Record, + { to, data, value, chainId }: PopulatedTransaction, + ) => { + const txChain = this.multiProvider.getChainName(chainId); + txChainToInnerCalls[txChain] ||= []; + txChainToInnerCalls[txChain].push({ to, data, value }); + return txChainToInnerCalls; + }, + {}, + ); + + const interchainAccountApp: InterchainAccount = buildInterchainAccountApp( + this.multiProvider, + this.props.chain, + this.props.config, + ); + + const transformedTxs: ethers.PopulatedTransaction[] = []; + for (const txChain of objKeys(txChainsToInnerCalls)) { transformedTxs.push( - this.props.interchainAccount.getCallRemote( - this.props.chain, - txChain, - txChainsToInnerCalls[txChain], - this.props.accountConfig, - this.props.hookMetadata, - ), + await interchainAccountApp.getCallRemote({ + chain: this.props.chain, + destination: txChain, + innerCalls: txChainsToInnerCalls[txChain], + config: this.props.config, + hookMetadata: this.props.hookMetadata, + }), ); - }); + } - return Promise.all(transformedTxs); + return transformedTxs; } } diff --git a/typescript/sdk/src/providers/transactions/transformer/ethersV5/EV5TxTransformerTypes.ts b/typescript/sdk/src/providers/transactions/transformer/ethersV5/EV5TxTransformerTypes.ts index e8c7eb06a..ed786ab79 100644 --- a/typescript/sdk/src/providers/transactions/transformer/ethersV5/EV5TxTransformerTypes.ts +++ b/typescript/sdk/src/providers/transactions/transformer/ethersV5/EV5TxTransformerTypes.ts @@ -1,10 +1,4 @@ -import { InterchainAccount } from '../../../../middleware/account/InterchainAccount.js'; -import { AccountConfig } from '../../../../middleware/account/types.js'; -import { ChainName } from '../../../../types.js'; +import { GetCallRemoteSettings } from '../../../../middleware/account/InterchainAccount.js'; -export interface EV5InterchainAccountTxTransformerProps { - chain: ChainName; - interchainAccount: InterchainAccount; - accountConfig: AccountConfig; - hookMetadata?: string; -} +export interface EV5InterchainAccountTxTransformerProps + extends Omit {} diff --git a/typescript/sdk/src/providers/transactions/types.ts b/typescript/sdk/src/providers/transactions/types.ts new file mode 100644 index 000000000..2c53d65a3 --- /dev/null +++ b/typescript/sdk/src/providers/transactions/types.ts @@ -0,0 +1,7 @@ +import { ethers } from 'ethers'; +import { z } from 'zod'; + +import { PopulatedTransactionSchema } from './schemas.js'; + +export type PopulatedTransaction = z.infer & + ethers.PopulatedTransaction; From eb23e772961107aef288119bc9aa81d0589ed429 Mon Sep 17 00:00:00 2001 From: Lee <6251863+ltyu@users.noreply.github.com> Date: Mon, 27 May 2024 15:15:21 -0400 Subject: [PATCH 04/73] feat: Create EvmCoreModule with create() (#3737) ### Description This Pull Request introduces the new EVM core module be implementing the `create()` function. Currently, `create()` will call the existing CoreDeployer ### Related issues - Fixes #3575 ### Backward compatibility Yes ### Testing Unit Tests --------- Signed-off-by: Paul Balaji Co-authored-by: Paul Balaji --- .changeset/beige-suns-wave.md | 5 + typescript/sdk/src/contracts/contracts.ts | 9 + .../src/core/EvmCoreModule.hardhat-test.ts | 158 ++++++++++ typescript/sdk/src/core/EvmCoreModule.ts | 276 ++++++++++++++++++ typescript/sdk/src/ism/EvmIsmModule.ts | 44 +-- 5 files changed, 455 insertions(+), 37 deletions(-) create mode 100644 .changeset/beige-suns-wave.md create mode 100644 typescript/sdk/src/core/EvmCoreModule.hardhat-test.ts create mode 100644 typescript/sdk/src/core/EvmCoreModule.ts diff --git a/.changeset/beige-suns-wave.md b/.changeset/beige-suns-wave.md new file mode 100644 index 000000000..277f99d47 --- /dev/null +++ b/.changeset/beige-suns-wave.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/sdk': minor +--- + +Add create() with EvmCoreModule diff --git a/typescript/sdk/src/contracts/contracts.ts b/typescript/sdk/src/contracts/contracts.ts index 557e68d17..7a58b3bfd 100644 --- a/typescript/sdk/src/contracts/contracts.ts +++ b/typescript/sdk/src/contracts/contracts.ts @@ -146,6 +146,15 @@ export function attachContractsMapAndGetForeignDeployments< }; } +export function attachAndConnectContracts( + addresses: HyperlaneAddresses, + factories: F, + connection: Connection, +): HyperlaneContracts { + const contracts = attachContracts(addresses, factories); + return connectContracts(contracts, connection); +} + export function connectContracts( contracts: HyperlaneContracts, connection: Connection, diff --git a/typescript/sdk/src/core/EvmCoreModule.hardhat-test.ts b/typescript/sdk/src/core/EvmCoreModule.hardhat-test.ts new file mode 100644 index 000000000..0d64c9080 --- /dev/null +++ b/typescript/sdk/src/core/EvmCoreModule.hardhat-test.ts @@ -0,0 +1,158 @@ +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers.js'; +import { expect } from 'chai'; +import { constants } from 'ethers'; +import hre from 'hardhat'; + +import { + Mailbox__factory, + ProxyAdmin__factory, + TestRecipient__factory, + TimelockController__factory, + ValidatorAnnounce__factory, +} from '@hyperlane-xyz/core'; +import { objMap } from '@hyperlane-xyz/utils'; + +import { TestChainName } from '../consts/testChains.js'; +import { MultiProvider } from '../providers/MultiProvider.js'; +import { testCoreConfig } from '../test/testUtils.js'; + +import { EvmCoreModule } from './EvmCoreModule.js'; + +describe('EvmCoreModule', async () => { + const DELAY = 1892391283182; + let signer: SignerWithAddress; + let multiProvider: MultiProvider; + let evmCoreModule: EvmCoreModule; + let proxyAdminContract: any; + let mailboxContract: any; + let validatorAnnounceContract: any; + let testRecipientContract: any; + let timelockControllerContract: any; + + before(async () => { + [signer] = await hre.ethers.getSigners(); + multiProvider = MultiProvider.createTestMultiProvider({ signer }); + const config = { + ...testCoreConfig([TestChainName.test1])[TestChainName.test1], + upgrade: { + timelock: { + delay: DELAY, + roles: { + executor: signer.address, + proposer: signer.address, + }, + }, + }, + }; + + evmCoreModule = await EvmCoreModule.create({ + chain: TestChainName.test1, + config, + multiProvider, + }); + + const { + proxyAdmin, + mailbox, + validatorAnnounce, + testRecipient, + timelockController, + } = evmCoreModule.serialize(); + + proxyAdminContract = ProxyAdmin__factory.connect( + proxyAdmin!, + multiProvider.getProvider(TestChainName.test1), + ); + + mailboxContract = Mailbox__factory.connect( + mailbox!, + multiProvider.getProvider(TestChainName.test1), + ); + + validatorAnnounceContract = ValidatorAnnounce__factory.connect( + validatorAnnounce!, + multiProvider.getProvider(TestChainName.test1), + ); + + testRecipientContract = TestRecipient__factory.connect( + testRecipient!, + multiProvider.getProvider(TestChainName.test1), + ); + + timelockControllerContract = TimelockController__factory.connect( + timelockController!, + multiProvider.getProvider(TestChainName.test1), + ); + }); + + describe('Create', async () => { + it('should create deploy an ICA', () => { + const { interchainAccountRouter, interchainAccountIsm } = + evmCoreModule.serialize(); + expect(interchainAccountIsm).to.exist; + expect(interchainAccountRouter).to.exist; + }); + + it('should deploy ISM factories', () => { + // Each ISM factory + objMap( + evmCoreModule.serialize().ismFactoryFactories, + (_: any, factoryAddress: any) => { + expect(factoryAddress).to.exist; + expect(factoryAddress).to.not.equal(constants.AddressZero); + }, + ); + }); + + it('should deploy proxyAdmin', () => { + expect(evmCoreModule.serialize().proxyAdmin).to.exist; + }); + + it('should set proxyAdmin owner to deployer', async () => { + expect(await proxyAdminContract.owner()).to.equal(signer.address); + }); + + it('should deploy mailbox', () => { + expect(evmCoreModule.serialize().mailbox).to.exist; + }); + + it('should set mailbox owner to proxyAdmin', async () => { + expect(await mailboxContract.owner()).to.equal( + evmCoreModule.serialize().proxyAdmin, + ); + }); + + it('should deploy mailbox default Ism', async () => { + expect(await mailboxContract.defaultIsm()).to.not.equal( + constants.AddressZero, + ); + }); + + it('should deploy mailbox default hook', async () => { + expect(await mailboxContract.defaultHook()).to.not.equal( + constants.AddressZero, + ); + }); + + it('should deploy mailbox required hook', async () => { + expect(await mailboxContract.requiredHook()).to.not.equal( + constants.AddressZero, + ); + }); + + it('should deploy validatorAnnounce', async () => { + expect(evmCoreModule.serialize().validatorAnnounce).to.exist; + expect(await validatorAnnounceContract.owner()).to.equal(signer.address); + }); + + it('should deploy testRecipient', async () => { + expect(evmCoreModule.serialize().testRecipient).to.exist; + expect(await testRecipientContract.owner()).to.equal(signer.address); + }); + + it('should deploy timelock if upgrade is set', async () => { + expect(evmCoreModule.serialize().timelockController).to.exist; + expect(await timelockControllerContract.getMinDelay()).to.equal(DELAY); + }); + }); +}); diff --git a/typescript/sdk/src/core/EvmCoreModule.ts b/typescript/sdk/src/core/EvmCoreModule.ts new file mode 100644 index 000000000..f4dd3ee10 --- /dev/null +++ b/typescript/sdk/src/core/EvmCoreModule.ts @@ -0,0 +1,276 @@ +import { Mailbox } from '@hyperlane-xyz/core'; +import { Address, ProtocolType, rootLogger } from '@hyperlane-xyz/utils'; + +import { + attachContractsMap, + serializeContractsMap, +} from '../contracts/contracts.js'; +import { HyperlaneAddresses } from '../contracts/types.js'; +import { CoreConfig } from '../core/types.js'; +import { HyperlaneProxyFactoryDeployer } from '../deploy/HyperlaneProxyFactoryDeployer.js'; +import { + ProxyFactoryFactories, + proxyFactoryFactories, +} from '../deploy/contracts.js'; +import { HyperlaneIsmFactory } from '../ism/HyperlaneIsmFactory.js'; +import { MultiProvider } from '../providers/MultiProvider.js'; +import { EthersV5Transaction } from '../providers/ProviderType.js'; +import { ChainNameOrId } from '../types.js'; + +import { + HyperlaneModule, + HyperlaneModuleArgs, +} from './AbstractHyperlaneModule.js'; +import { EvmCoreReader } from './EvmCoreReader.js'; +import { EvmIcaModule } from './EvmIcaModule.js'; +import { HyperlaneCoreDeployer } from './HyperlaneCoreDeployer.js'; +import { CoreFactories } from './contracts.js'; + +type DeployedAdresses = HyperlaneAddresses & { + testRecipient: Address; + timelockController?: Address; // Can be optional because it is only deployed if config.upgrade = true + interchainAccountRouter: Address; + interchainAccountIsm: Address; + ismFactoryFactories: HyperlaneAddresses; +}; + +export class EvmCoreModule extends HyperlaneModule< + ProtocolType.Ethereum, + CoreConfig, + DeployedAdresses +> { + protected logger = rootLogger.child({ module: 'EvmCoreModule' }); + protected coreReader: EvmCoreReader; + public readonly chainName: string; + + protected constructor( + protected readonly multiProvider: MultiProvider, + args: HyperlaneModuleArgs, + ) { + super(args); + this.coreReader = new EvmCoreReader(multiProvider, this.args.chain); + this.chainName = this.multiProvider.getChainName(this.args.chain); + } + + /** + * Reads the core configuration from the mailbox address specified in the SDK arguments. + * @returns The core config. + */ + public async read(): Promise { + return this.coreReader.deriveCoreConfig(this.args.addresses.mailbox); + } + + public async update(_config: CoreConfig): Promise { + throw new Error('Method not implemented.'); + } + + /** + * Deploys the Core contracts. + * @remark Most of the contract owners is the Deployer with some being the Proxy Admin. + * @returns The created EvmCoreModule instance. + */ + public static async create(params: { + chain: ChainNameOrId; + config: CoreConfig; + multiProvider: MultiProvider; + }): Promise { + const { chain, config, multiProvider } = params; + const addresses = await EvmCoreModule.deploy({ + config, + multiProvider, + chain, + }); + + // Create CoreModule and deploy the Core contracts + const module = new EvmCoreModule(multiProvider, { + addresses, + chain, + config, + }); + + return module; + } + + /** + * Deploys the core Hyperlane contracts. + * @returns The deployed core contract addresses. + */ + static async deploy(params: { + config: CoreConfig; + multiProvider: MultiProvider; + chain: ChainNameOrId; + }): Promise { + const { config, multiProvider, chain } = params; + const chainName = multiProvider.getChainName(chain); + + // Deploy Ism Factories + const ismFactoryFactories = await EvmCoreModule.deployIsmFactories({ + chainName, + config, + multiProvider, + }); + + // Deploy IsmFactory to be used in CoreDeployer + const ismFactory = new HyperlaneIsmFactory( + attachContractsMap( + { [chainName]: ismFactoryFactories }, + proxyFactoryFactories, + ), + multiProvider, + ); + + // Initalize Deployer + const coreDeployer = new HyperlaneCoreDeployer(multiProvider, ismFactory); + + // Deploy proxyAdmin + const proxyAdmin = ( + await coreDeployer.deployContract(chainName, 'proxyAdmin', []) + ).address; + + // Deploy Mailbox + const mailbox = await this.deployMailbox({ + config, + coreDeployer, + proxyAdmin, + multiProvider, + chain, + }); + + // Deploy ICA ISM and Router + const { interchainAccountRouter, interchainAccountIsm } = ( + await EvmIcaModule.create({ + chain: chainName, + multiProvider: multiProvider, + config: { + mailbox: mailbox.address, + owner: await multiProvider.getSigner(chain).getAddress(), + }, + }) + ).serialize(); + + // Deploy Validator announce + const validatorAnnounce = ( + await coreDeployer.deployValidatorAnnounce(chainName, mailbox.address) + ).address; + + // Deploy timelock controller if config.upgrade is set + let timelockController; + if (config.upgrade) { + timelockController = ( + await coreDeployer.deployTimelock(chainName, config.upgrade.timelock) + ).address; + } + + // Deploy Test Receipient + const testRecipient = ( + await coreDeployer.deployTestRecipient( + chainName, + await mailbox.defaultIsm(), + ) + ).address; + + // Set Core & extra addresses + return { + ismFactoryFactories, + proxyAdmin, + mailbox: mailbox.address, + interchainAccountRouter, + interchainAccountIsm, + validatorAnnounce, + timelockController, + testRecipient, + }; + } + + /** + * Deploys the ISM factories for a given chain. + * @returns The deployed ISM factories addresses. + */ + static async deployIsmFactories(params: { + chainName: string; + config: CoreConfig; + multiProvider: MultiProvider; + }): Promise> { + const { chainName, config, multiProvider } = params; + + // ChainMap is still needed for HyperlaneIsmFactory + const proxyFactoryDeployer = new HyperlaneProxyFactoryDeployer( + multiProvider, + ); + const ismFactoriesFactory = await proxyFactoryDeployer.deploy({ + [chainName]: config, + }); + + return serializeContractsMap(ismFactoriesFactory)[chainName]; + } + + /** + * Deploys a Mailbox and its default ISM, hook, and required hook contracts with a given configuration. + * @returns The deployed Mailbox contract instance. + */ + static async deployMailbox(params: { + config: CoreConfig; + proxyAdmin: Address; + coreDeployer: HyperlaneCoreDeployer; + multiProvider: MultiProvider; + chain: ChainNameOrId; + }): Promise { + const { + config, + proxyAdmin, + coreDeployer: deployer, + multiProvider, + chain, + } = params; + const chainName = multiProvider.getChainName(chain); + + const domain = multiProvider.getDomainId(chainName); + const mailbox = await deployer.deployProxiedContract( + chainName, + 'mailbox', + 'mailbox', + proxyAdmin, + [domain], + ); + + // @todo refactor when 1) IsmModule is ready + const deployedDefaultIsm = await deployer.deployIsm( + chainName, + config.defaultIsm, + mailbox.address, + ); + + // @todo refactor when 1) HookModule is ready, and 2) Hooks Config can handle strings + const deployedDefaultHook = await deployer.deployHook( + chainName, + config.defaultHook, + { + mailbox: mailbox.address, + proxyAdmin, + }, + ); + + // @todo refactor when 1) HookModule is ready, and 2) Hooks Config can handle strings + const deployedRequiredHook = await deployer.deployHook( + chainName, + config.requiredHook, + { + mailbox: mailbox.address, + proxyAdmin, + }, + ); + + // Initialize Mailbox + await multiProvider.handleTx( + chain, + mailbox.initialize( + proxyAdmin, + deployedDefaultIsm, + deployedDefaultHook.address, + deployedRequiredHook.address, + multiProvider.getTransactionOverrides(chain), + ), + ); + return mailbox; + } +} diff --git a/typescript/sdk/src/ism/EvmIsmModule.ts b/typescript/sdk/src/ism/EvmIsmModule.ts index 7d9921832..c82b4194d 100644 --- a/typescript/sdk/src/ism/EvmIsmModule.ts +++ b/typescript/sdk/src/ism/EvmIsmModule.ts @@ -1,6 +1,6 @@ import { Address, ProtocolType, rootLogger } from '@hyperlane-xyz/utils'; -import { HyperlaneContracts } from '../contracts/types.js'; +import { HyperlaneAddresses } from '../contracts/types.js'; import { HyperlaneModule, HyperlaneModuleArgs, @@ -11,79 +11,49 @@ import { MultiProvider } from '../providers/MultiProvider.js'; import { EthersV5Transaction } from '../providers/ProviderType.js'; import { ChainNameOrId } from '../types.js'; -import { EvmIsmCreator } from './EvmIsmCreator.js'; import { EvmIsmReader } from './EvmIsmReader.js'; import { IsmConfig } from './types.js'; export class EvmIsmModule extends HyperlaneModule< ProtocolType.Ethereum, IsmConfig, - HyperlaneContracts & { + HyperlaneAddresses & { deployedIsm: Address; } > { protected logger = rootLogger.child({ module: 'EvmIsmModule' }); protected reader: EvmIsmReader; - protected creator: EvmIsmCreator; protected constructor( protected readonly multiProvider: MultiProvider, protected readonly deployer: HyperlaneDeployer, args: HyperlaneModuleArgs< IsmConfig, - HyperlaneContracts & { + HyperlaneAddresses & { deployedIsm: Address; } >, ) { super(args); this.reader = new EvmIsmReader(multiProvider, args.chain); - this.creator = new EvmIsmCreator(deployer, multiProvider, args.addresses); } public async read(): Promise { return await this.reader.deriveIsmConfig(this.args.addresses.deployedIsm); } - public async update(config: IsmConfig): Promise { + public async update(_config: IsmConfig): Promise { throw new Error('Method not implemented.'); - - const destination = this.multiProvider.getChainName(this.args.chain); - await this.creator.update({ - destination, - config, - existingIsmAddress: this.args.addresses.deployedIsm, - }); - return []; } // manually write static create function - public static async create({ - chain, - config, - deployer, - factories, - multiProvider, - }: { + public static async create(_params: { chain: ChainNameOrId; config: IsmConfig; deployer: HyperlaneDeployer; - factories: HyperlaneContracts; + factories: HyperlaneAddresses; multiProvider: MultiProvider; }): Promise { - const destination = multiProvider.getChainName(chain); - const ismCreator = new EvmIsmCreator(deployer, multiProvider, factories); - const deployedIsm = await ismCreator.deploy({ - config, - destination, - }); - return new EvmIsmModule(multiProvider, deployer, { - addresses: { - ...factories, - deployedIsm: deployedIsm.address, - }, - chain, - config, - }); + throw new Error('Method not implemented.'); } } From 49f41d9759fd515bfd89e6e22e799c41b27b4119 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Wed, 29 May 2024 16:17:19 -0400 Subject: [PATCH 05/73] chore: Merge branch 'main' into cli-2.0 (#3863) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: Kunal Arora <55632507+aroralanuk@users.noreply.github.com> Co-authored-by: Connor McEwen Co-authored-by: Trevor Porter Co-authored-by: byeongsu-hong Co-authored-by: Avi Atkin <103125634+avious00@users.noreply.github.com> Co-authored-by: Daniel Savu <23065004+daniel-savu@users.noreply.github.com> Co-authored-by: J M Rossy Co-authored-by: Ali Alaoui Co-authored-by: Nam Chu Hoai Co-authored-by: Paul Balaji Co-authored-by: omahs <73983677+omahs@users.noreply.github.com> --- .github/CODEOWNERS | 8 +- .github/workflows/monorepo-docker.yml | 5 + .github/workflows/test.yml | 93 +- Dockerfile | 7 + README.md | 4 +- rust/.cargo/config.toml | 2 + rust/Cargo.lock | 109 +- rust/Cargo.toml | 6 +- rust/Dockerfile | 2 +- rust/agents/relayer/.cargo/config.toml | 2 + rust/agents/relayer/Cargo.toml | 3 + rust/agents/relayer/src/msg/op_submitter.rs | 65 +- rust/agents/relayer/src/msg/processor.rs | 345 ++- rust/agents/relayer/src/processor.rs | 7 +- rust/agents/relayer/src/relayer.rs | 83 +- .../relayer/src/settings/matching_list.rs | 6 +- rust/agents/relayer/src/settings/mod.rs | 2 +- rust/agents/scraper/Cargo.toml | 1 + rust/agents/scraper/src/agent.rs | 1 + rust/agents/scraper/src/settings.rs | 2 +- rust/agents/validator/Cargo.toml | 1 + rust/agents/validator/src/settings.rs | 2 +- rust/agents/validator/src/validator.rs | 1 + .../src/contracts/mailbox.rs | 2 +- rust/config/mainnet_config.json | 216 +- rust/hyperlane-base/Cargo.toml | 1 + rust/hyperlane-base/src/agent.rs | 12 +- .../cursors/sequence_aware/backward.rs | 26 +- .../cursors/sequence_aware/forward.rs | 22 +- rust/hyperlane-base/src/contract_sync/mod.rs | 2 +- .../src/db/rocks/hyperlane_db.rs | 58 +- rust/hyperlane-base/src/settings/mod.rs | 2 +- .../hyperlane-base/src/settings/parser/mod.rs | 2 +- rust/hyperlane-base/src/settings/trace/mod.rs | 7 +- solidity/.solhint.json | 1 - solidity/.solhintignore | 1 + solidity/CHANGELOG.md | 12 + solidity/README.md | 2 +- solidity/contracts/avs/ECDSAStakeRegistry.sol | 148 +- .../avs/ECDSAStakeRegistryStorage.sol | 6 +- .../IECDSAStakeRegistryEventsAndErrors.sol | 17 +- solidity/contracts/test/ERC20Test.sol | 12 + ...atTokenCollateral.sol => HypFiatToken.sol} | 2 +- ...{HypXERC20Collateral.sol => HypXERC20.sol} | 2 +- .../token/extensions/HypXERC20Lockbox.sol | 54 + .../contracts/token/interfaces/IXERC20.sol | 15 + .../token/interfaces/IXERC20Lockbox.sol | 61 + solidity/package.json | 4 +- solidity/script/avs/DeployAVS.s.sol | 30 +- solidity/script/avs/eigenlayer_addresses.json | 2 + .../test/avs/HyperlaneServiceManager.t.sol | 17 +- solidity/test/token/HypERC20.t.sol | 22 +- typescript/ccip-server/CHANGELOG.md | 2 + typescript/ccip-server/package.json | 2 +- typescript/cli/CHANGELOG.md | 18 + typescript/cli/README.md | 2 +- typescript/cli/cli.ts | 4 + typescript/cli/package.json | 11 +- typescript/cli/src/avs/config.ts | 19 + typescript/cli/src/avs/stakeRegistry.ts | 164 ++ typescript/cli/src/commands/avs.ts | 84 + typescript/cli/src/commands/config.ts | 2 +- typescript/cli/src/commands/options.ts | 32 + typescript/cli/src/commands/validator.ts | 51 + typescript/cli/src/config/chain.ts | 6 +- typescript/cli/src/config/warp.ts | 185 +- typescript/cli/src/consts.ts | 1 + typescript/cli/src/context/context.ts | 35 +- typescript/cli/src/deploy/core.ts | 12 +- typescript/cli/src/deploy/utils.ts | 30 - typescript/cli/src/deploy/warp.ts | 236 +- typescript/cli/src/registry/MergedRegistry.ts | 156 -- typescript/cli/src/send/message.ts | 9 +- typescript/cli/src/send/transfer.ts | 9 +- typescript/cli/src/tests/deployTestErc20.ts | 9 +- typescript/cli/src/utils/chains.ts | 16 +- typescript/cli/src/utils/env.ts | 3 + typescript/cli/src/utils/files.ts | 9 + typescript/cli/src/validator/address.ts | 166 ++ typescript/cli/src/version.ts | 2 +- typescript/helloworld/CHANGELOG.md | 12 + typescript/helloworld/package.json | 8 +- typescript/infra/CHANGELOG.md | 18 + .../config/environments/mainnet3/agent.ts | 46 +- .../mainnet3/aw-validators/hyperlane.json | 6 + .../mainnet3/aw-validators/rc.json | 6 + .../config/environments/mainnet3/chains.ts | 2 +- .../mainnet3/core/verification.json | 2120 ++++++++++++----- .../config/environments/mainnet3/funding.ts | 26 +- .../environments/mainnet3/gasPrices.json | 10 +- .../environments/mainnet3/helloworld.ts | 4 +- .../environments/mainnet3/infrastructure.ts | 4 +- .../mainnet3/ism/verification.json | 124 + .../environments/mainnet3/liquidityLayer.ts | 2 +- .../config/environments/mainnet3/owners.ts | 1 + .../mainnet3/supportedChainNames.ts | 14 +- .../environments/mainnet3/tokenPrices.json | 42 +- .../environments/mainnet3/validators.ts | 28 + .../config/environments/testnet4/funding.ts | 12 +- .../environments/testnet4/helloworld.ts | 4 +- .../environments/testnet4/infrastructure.ts | 4 +- .../environments/testnet4/middleware.ts | 2 +- typescript/infra/config/registry.ts | 22 +- typescript/infra/config/warp.ts | 9 +- typescript/infra/package.json | 10 +- typescript/infra/scripts/agent-utils.ts | 7 + .../infra/scripts/announce-validators.ts | 18 +- typescript/infra/scripts/deploy.ts | 7 +- .../funding/fund-keys-from-deployer.ts | 16 +- .../list-validator-checkpoint-indices.ts | 4 +- typescript/infra/scripts/verify-validators.ts | 8 +- typescript/infra/src/agents/aws/validator.ts | 75 +- .../infra/src/config/agent/validator.ts | 7 +- typescript/infra/src/config/gas-oracle.ts | 4 +- typescript/infra/src/deployment/deploy.ts | 7 +- .../infra/src/govern/HyperlaneAppGovernor.ts | 14 +- typescript/infra/test/agent-configs.test.ts | 4 +- typescript/infra/test/govern.hardhat-test.ts | 16 +- typescript/sdk/CHANGELOG.md | 16 + typescript/sdk/README.md | 15 - typescript/sdk/package.json | 7 +- .../{infra/src/agents => sdk/src}/aws/s3.ts | 51 +- typescript/sdk/src/aws/validator.ts | 101 + typescript/sdk/src/consts/multisigIsm.ts | 17 + .../sdk/src/core/CoreDeployer.hardhat-test.ts | 6 +- typescript/sdk/src/core/HyperlaneCore.ts | 66 +- .../sdk/src/core/HyperlaneCoreDeployer.ts | 17 +- .../sdk/src/deploy/HyperlaneDeployer.ts | 56 +- typescript/sdk/src/deploy/schemas.ts | 4 +- typescript/sdk/src/deploy/types.ts | 2 +- .../sdk/src/deploy/verify/ContractVerifier.ts | 4 +- typescript/sdk/src/hook/EvmHookReader.ts | 5 +- .../sdk/src/hook/HyperlaneHookDeployer.ts | 18 +- typescript/sdk/src/index.ts | 35 +- typescript/sdk/src/ism/EvmIsmReader.ts | 32 +- .../ism/HyperlaneIsmFactory.hardhat-test.ts | 47 +- .../sdk/src/ism/metadata/aggregation.test.ts | 28 +- .../sdk/src/ism/metadata/aggregation.ts | 103 +- .../src/ism/metadata/builder.hardhat-test.ts | 180 ++ typescript/sdk/src/ism/metadata/builder.ts | 133 ++ .../sdk/src/ism/metadata/multisig.test.ts | 12 +- typescript/sdk/src/ism/metadata/multisig.ts | 198 +- typescript/sdk/src/ism/metadata/null.ts | 35 + typescript/sdk/src/ism/metadata/routing.ts | 58 + typescript/sdk/src/ism/schemas.test.ts | 3 +- typescript/sdk/src/ism/schemas.ts | 2 +- typescript/sdk/src/ism/types.ts | 13 +- typescript/sdk/src/metadata/agentConfig.ts | 2 +- .../sdk/src/middleware/account/schemas.ts | 3 +- .../sdk/src/router/GasRouterDeployer.ts | 9 +- .../sdk/src/router/ProxiedRouterDeployer.ts | 37 +- typescript/sdk/src/router/schemas.ts | 24 +- typescript/sdk/src/router/types.ts | 28 +- typescript/sdk/src/test/testUtils.ts | 4 - .../sdk/src/token/EvmERC20WarpRouteReader.ts | 99 +- typescript/sdk/src/token/Token.test.ts | 27 - typescript/sdk/src/token/Token.ts | 7 +- typescript/sdk/src/token/TokenStandard.ts | 17 +- .../token/adapters/CosmWasmTokenAdapter.ts | 4 +- .../src/token/adapters/CosmosTokenAdapter.ts | 4 +- .../sdk/src/token/adapters/EvmTokenAdapter.ts | 13 +- .../sdk/src/token/adapters/ITokenAdapter.ts | 4 +- .../token/adapters/SealevelTokenAdapter.ts | 13 +- typescript/sdk/src/token/app.ts | 8 +- typescript/sdk/src/token/checker.ts | 15 +- typescript/sdk/src/token/config.ts | 102 +- typescript/sdk/src/token/contracts.ts | 25 +- .../sdk/src/token/deploy.hardhat-test.ts | 121 +- typescript/sdk/src/token/deploy.ts | 436 +--- typescript/sdk/src/token/schemas.test.ts | 69 +- typescript/sdk/src/token/schemas.ts | 93 +- typescript/sdk/src/token/types.ts | 2 + typescript/sdk/src/utils/schemas.ts | 6 + typescript/utils/CHANGELOG.md | 6 + typescript/utils/package.json | 2 +- typescript/utils/src/arrays.ts | 6 + typescript/utils/src/index.ts | 14 +- typescript/utils/src/math.ts | 4 + typescript/utils/src/objects.ts | 18 + typescript/utils/src/types.ts | 42 +- typescript/utils/src/validator.ts | 66 +- yarn.lock | 1375 ++++++++++- 182 files changed, 6941 insertions(+), 2541 deletions(-) create mode 100644 rust/.cargo/config.toml create mode 100644 rust/agents/relayer/.cargo/config.toml rename solidity/contracts/token/extensions/{HypFiatTokenCollateral.sol => HypFiatToken.sol} (94%) rename solidity/contracts/token/extensions/{HypXERC20Collateral.sol => HypXERC20.sol} (93%) create mode 100644 solidity/contracts/token/extensions/HypXERC20Lockbox.sol create mode 100644 solidity/contracts/token/interfaces/IXERC20Lockbox.sol create mode 100644 typescript/cli/src/avs/config.ts create mode 100644 typescript/cli/src/avs/stakeRegistry.ts create mode 100644 typescript/cli/src/commands/avs.ts create mode 100644 typescript/cli/src/commands/validator.ts delete mode 100644 typescript/cli/src/registry/MergedRegistry.ts create mode 100644 typescript/cli/src/validator/address.ts rename typescript/{infra/src/agents => sdk/src}/aws/s3.ts (53%) create mode 100644 typescript/sdk/src/aws/validator.ts create mode 100644 typescript/sdk/src/ism/metadata/builder.hardhat-test.ts create mode 100644 typescript/sdk/src/ism/metadata/builder.ts create mode 100644 typescript/sdk/src/ism/metadata/null.ts create mode 100644 typescript/sdk/src/ism/metadata/routing.ts create mode 100644 typescript/sdk/src/utils/schemas.ts diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 6a5ccb733..ab8ed10d1 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,9 +1,9 @@ # File extension owners *.sol @yorhodes @tkporter @aroralanuk @nbayindirli -*.ts @yorhodes @jmrossy @nambrot @nbayindirli +*.ts @yorhodes @jmrossy @nbayindirli *.rs @tkporter @daniel-savu -*.md @Skunkchain @nambrot @avious00 +*.md @Skunkchain @avious00 # Package owners @@ -20,10 +20,10 @@ typescript/sdk @yorhodes @jmrossy typescript/token @yorhodes @jmrossy @tkporter @aroralanuk @nbayindirli ## Hello World -typescript/helloworld @yorhodes @nambrot +typescript/helloworld @yorhodes ## CLI typescript/cli @jmrossy @yorhodes @aroralanuk @nbayindirli ## Infra -typescript/infra @tkporter @nambrot +typescript/infra @tkporter diff --git a/.github/workflows/monorepo-docker.yml b/.github/workflows/monorepo-docker.yml index 72a0fc1f1..b9d90db2b 100644 --- a/.github/workflows/monorepo-docker.yml +++ b/.github/workflows/monorepo-docker.yml @@ -8,6 +8,8 @@ on: paths: # For now, because this image is only used to use `infra`, we just build for infra changes - 'typescript/infra/**' + - 'Dockerfile' + - '.dockerignore' concurrency: group: build-push-monorepo-${{ github.ref }} cancel-in-progress: true @@ -74,3 +76,6 @@ jobs: labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max + # To always fetch the latest registry, we use the date as the cache key + build-args: | + REGISTRY_CACHE=${{ steps.taggen.outputs.TAG_DATE }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4a4727d03..b6bb18d5f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,7 +19,8 @@ env: LOG_FORMAT: PRETTY CARGO_TERM_COLOR: always RUST_BACKTRACE: full - REGISTRY_URI: ../../node_modules/@hyperlane-xyz/registry/dist + # Alongside the monorepo in the directory above the $GITHUB_WORKSPACE. + REGISTRY_URI: ${{ github.workspace }}/../hyperlane-registry jobs: yarn-install: @@ -81,6 +82,31 @@ jobs: - name: build run: yarn build + checkout-registry: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + repository: hyperlane-xyz/hyperlane-registry + ref: main + path: ./hyperlane-registry + + # Put alongside the monorepo in the directory above the $GITHUB_WORKSPACE. + # actions/checkout doesn't allow you to checkout a repository outside of the workspace. + # See https://github.com/actions/checkout/issues/197. + - run: mv ./hyperlane-registry ../ + + # A workaround for relative paths not being supported by actions/cache. + # See https://github.com/actions/upload-artifact/issues/176#issuecomment-1367855630. + - run: echo "REGISTRY_URI_ABSOLUTE=$(realpath $REGISTRY_URI)" >> $GITHUB_ENV + + - name: registry-cache + uses: actions/cache@v3 + with: + path: | + ${{ env.REGISTRY_URI_ABSOLUTE }} + key: hyperlane-registry-${{ github.event.pull_request.head.sha || github.sha }} + lint-prettier: runs-on: ubuntu-latest needs: [yarn-install] @@ -113,7 +139,7 @@ jobs: yarn-test: runs-on: ubuntu-latest - needs: [yarn-build] + needs: [yarn-build, checkout-registry] steps: - uses: actions/checkout@v3 with: @@ -132,12 +158,23 @@ jobs: !./rust key: ${{ github.event.pull_request.head.sha || github.sha }} + # A workaround for relative paths not being supported by actions/cache. + # See https://github.com/actions/upload-artifact/issues/176#issuecomment-1367855630. + - run: echo "REGISTRY_URI_ABSOLUTE=$(realpath $REGISTRY_URI)" >> $GITHUB_ENV + + - name: registry-cache + uses: actions/cache@v3 + with: + path: | + ${{ env.REGISTRY_URI_ABSOLUTE }} + key: hyperlane-registry-${{ github.event.pull_request.head.sha || github.sha }} + - name: Unit Tests run: yarn test:ci agent-configs: runs-on: ubuntu-latest - needs: [yarn-build] + needs: [yarn-build, checkout-registry] strategy: fail-fast: false matrix: @@ -164,6 +201,17 @@ jobs: !./rust key: ${{ github.event.pull_request.head.sha || github.sha }} + # A workaround for relative paths not being supported by actions/cache. + # See https://github.com/actions/upload-artifact/issues/176#issuecomment-1367855630. + - run: echo "REGISTRY_URI_ABSOLUTE=$(realpath $REGISTRY_URI)" >> $GITHUB_ENV + + - name: registry-cache + uses: actions/cache@v3 + with: + path: | + ${{ env.REGISTRY_URI_ABSOLUTE }} + key: hyperlane-registry-${{ github.event.pull_request.head.sha || github.sha }} + - name: Generate ${{ matrix.environment }} agent config run: | cd typescript/infra @@ -177,7 +225,7 @@ jobs: e2e-matrix: runs-on: larger-runner if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.base_ref == 'main') - needs: [yarn-build] + needs: [yarn-build, checkout-registry] strategy: matrix: e2e-type: [cosmwasm, non-cosmwasm] @@ -230,6 +278,17 @@ jobs: !./rust key: ${{ github.event.pull_request.head.sha || github.sha }} + # A workaround for relative paths not being supported by actions/cache. + # See https://github.com/actions/upload-artifact/issues/176#issuecomment-1367855630. + - run: echo "REGISTRY_URI_ABSOLUTE=$(realpath $REGISTRY_URI)" >> $GITHUB_ENV + + - name: registry-cache + uses: actions/cache@v3 + with: + path: | + ${{ env.REGISTRY_URI_ABSOLUTE }} + key: hyperlane-registry-${{ github.event.pull_request.head.sha || github.sha }} + - name: cargo-cache uses: actions/cache@v3 with: @@ -267,7 +326,7 @@ jobs: cli-e2e: runs-on: larger-runner if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.base_ref == 'main') - needs: [yarn-build] + needs: [yarn-build, checkout-registry] strategy: matrix: include: @@ -323,6 +382,17 @@ jobs: !./rust key: ${{ github.event.pull_request.head.sha || github.sha }} + # A workaround for relative paths not being supported by actions/cache. + # See https://github.com/actions/upload-artifact/issues/176#issuecomment-1367855630. + - run: echo "REGISTRY_URI_ABSOLUTE=$(realpath $REGISTRY_URI)" >> $GITHUB_ENV + + - name: registry-cache + uses: actions/cache@v3 + with: + path: | + ${{ env.REGISTRY_URI_ABSOLUTE }} + key: hyperlane-registry-${{ github.event.pull_request.head.sha || github.sha }} + - name: cargo-cache uses: actions/cache@v3 with: @@ -335,7 +405,7 @@ jobs: env-test: runs-on: ubuntu-latest - needs: [yarn-build] + needs: [yarn-build, checkout-registry] strategy: fail-fast: false matrix: @@ -363,6 +433,17 @@ jobs: !./rust key: ${{ github.event.pull_request.head.sha || github.sha }} + # A workaround for relative paths not being supported by actions/cache. + # See https://github.com/actions/upload-artifact/issues/176#issuecomment-1367855630. + - run: echo "REGISTRY_URI_ABSOLUTE=$(realpath $REGISTRY_URI)" >> $GITHUB_ENV + + - name: registry-cache + uses: actions/cache@v3 + with: + path: | + ${{ env.REGISTRY_URI_ABSOLUTE }} + key: hyperlane-registry-${{ 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 }} diff --git a/Dockerfile b/Dockerfile index 4a01616b8..763e1186f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,3 +27,10 @@ COPY typescript ./typescript COPY solidity ./solidity RUN yarn build + +ENV REGISTRY_URI="/hyperlane-registry" +# To allow us to avoid caching the registry clone, we use a build-time arg to force +# the below steps to be re-run if this arg is changed. +ARG REGISTRY_CACHE="default" + +RUN git clone https://github.com/hyperlane-xyz/hyperlane-registry.git "$REGISTRY_URI" diff --git a/README.md b/README.md index 2d2d770c6..04551feee 100644 --- a/README.md +++ b/README.md @@ -14,9 +14,7 @@ Note this is the branch for Hyperlane v3. -V2 is still in operation but is not being actively developed. The code for V2 can be found in the [v2](https://github.com/hyperlane-xyz/hyperlane-monorepo/tree/v2) branch. - -V1 has since been deprecated in favor of V2, but if you are looking for code relating to the existing V1 deployments, refer to the [v1](https://github.com/hyperlane-xyz/hyperlane-monorepo/tree/v1) branch. +V2 is deprecated in favor of V3. The code for V2 can be found in the [v2](https://github.com/hyperlane-xyz/hyperlane-monorepo/tree/v2) branch. For V1 code, refer to the [v1](https://github.com/hyperlane-xyz/hyperlane-monorepo/tree/v1) branch. ## Overview diff --git a/rust/.cargo/config.toml b/rust/.cargo/config.toml new file mode 100644 index 000000000..bff29e6e1 --- /dev/null +++ b/rust/.cargo/config.toml @@ -0,0 +1,2 @@ +[build] +rustflags = ["--cfg", "tokio_unstable"] diff --git a/rust/Cargo.lock b/rust/Cargo.lock index d8b69f4ea..f29c2c80e 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -1347,6 +1347,43 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "console-api" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd326812b3fd01da5bb1af7d340d0d555fd3d4b641e7f1dfcf5962a902952787" +dependencies = [ + "futures-core", + "prost 0.12.4", + "prost-types 0.12.4", + "tonic 0.10.2", + "tracing-core", +] + +[[package]] +name = "console-subscriber" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7481d4c57092cd1c19dd541b92bdce883de840df30aa5d03fd48a3935c01842e" +dependencies = [ + "console-api", + "crossbeam-channel", + "crossbeam-utils", + "futures-task", + "hdrhistogram", + "humantime", + "prost-types 0.12.4", + "serde", + "serde_json", + "thread_local", + "tokio", + "tokio-stream", + "tonic 0.10.2", + "tracing", + "tracing-core", + "tracing-subscriber", +] + [[package]] name = "console_error_panic_hook" version = "0.1.7" @@ -1451,7 +1488,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73c9d2043a9e617b0d602fbc0a0ecd621568edbf3a9774890a6d562389bd8e1c" dependencies = [ "prost 0.11.9", - "prost-types", + "prost-types 0.11.9", "tendermint-proto 0.32.2 (registry+https://github.com/rust-lang/crates.io-index)", "tonic 0.9.2", ] @@ -3793,6 +3830,19 @@ dependencies = [ "hashbrown 0.14.3", ] +[[package]] +name = "hdrhistogram" +version = "7.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "765c9198f173dd59ce26ff9f95ef0aafd0a0fe01fb9d72841bc5066a4c06511d" +dependencies = [ + "base64 0.21.7", + "byteorder", + "flate2", + "nom", + "num-traits", +] + [[package]] name = "headers" version = "0.3.9" @@ -4116,6 +4166,7 @@ dependencies = [ "bs58 0.5.0", "color-eyre", "config", + "console-subscriber", "convert_case 0.6.0", "derive-new", "derive_builder", @@ -4946,7 +4997,7 @@ dependencies = [ "cosmwasm-std", "osmosis-std-derive", "prost 0.11.9", - "prost-types", + "prost-types 0.11.9", "schemars", "serde", "serde-cw-value", @@ -6534,16 +6585,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" dependencies = [ "bytes", - "prost-derive", + "prost-derive 0.11.9", ] [[package]] name = "prost" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c289cda302b98a28d40c8b3b90498d6e526dd24ac2ecea73e4e491685b94a" +checksum = "d0f5d036824e4761737860779c906171497f6d55681139d8312388f8fe398922" dependencies = [ "bytes", + "prost-derive 0.12.5", ] [[package]] @@ -6559,6 +6611,19 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "prost-derive" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9554e3ab233f0a932403704f1a1d08c30d5ccd931adfdfa1e8b5a19b52c1d55a" +dependencies = [ + "anyhow", + "itertools 0.12.0", + "proc-macro2 1.0.76", + "quote 1.0.35", + "syn 2.0.48", +] + [[package]] name = "prost-types" version = "0.11.9" @@ -6568,6 +6633,15 @@ dependencies = [ "prost 0.11.9", ] +[[package]] +name = "prost-types" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3235c33eb02c1f1e212abdbe34c78b264b038fb58ca612664343271e36e55ffe" +dependencies = [ + "prost 0.12.4", +] + [[package]] name = "protobuf" version = "2.28.0" @@ -6944,6 +7018,7 @@ dependencies = [ "async-trait", "axum", "config", + "console-subscriber", "convert_case 0.6.0", "derive-new", "derive_more", @@ -6957,6 +7032,7 @@ dependencies = [ "hyperlane-ethereum", "hyperlane-test", "itertools 0.12.0", + "mockall", "num-derive 0.4.1", "num-traits", "once_cell", @@ -6968,6 +7044,7 @@ dependencies = [ "strum 0.25.0", "thiserror", "tokio", + "tokio-metrics", "tokio-test", "tracing", "tracing-futures", @@ -7630,6 +7707,7 @@ version = "0.1.0" dependencies = [ "async-trait", "config", + "console-subscriber", "derive_more", "ethers", "eyre", @@ -9700,7 +9778,7 @@ dependencies = [ "num-traits", "once_cell", "prost 0.11.9", - "prost-types", + "prost-types 0.11.9", "ripemd", "serde", "serde_bytes", @@ -9739,7 +9817,7 @@ dependencies = [ "num-derive 0.3.3", "num-traits", "prost 0.11.9", - "prost-types", + "prost-types 0.11.9", "serde", "serde_bytes", "subtle-encoding", @@ -9756,7 +9834,7 @@ dependencies = [ "num-derive 0.3.3", "num-traits", "prost 0.11.9", - "prost-types", + "prost-types 0.11.9", "serde", "serde_bytes", "subtle-encoding", @@ -9956,6 +10034,7 @@ dependencies = [ "signal-hook-registry", "socket2 0.5.5", "tokio-macros", + "tracing", "windows-sys 0.48.0", ] @@ -9980,6 +10059,17 @@ dependencies = [ "syn 2.0.48", ] +[[package]] +name = "tokio-metrics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eace09241d62c98b7eeb1107d4c5c64ca3bd7da92e8c218c153ab3a78f9be112" +dependencies = [ + "futures-util", + "pin-project-lite", + "tokio-stream", +] + [[package]] name = "tokio-native-tls" version = "0.3.1" @@ -10217,7 +10307,7 @@ dependencies = [ "hyper-timeout", "percent-encoding", "pin-project", - "prost 0.12.3", + "prost 0.12.4", "rustls 0.21.10", "rustls-native-certs 0.6.3", "rustls-pemfile 1.0.4", @@ -10638,6 +10728,7 @@ dependencies = [ "async-trait", "axum", "config", + "console-subscriber", "derive-new", "derive_more", "ethers", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 0322d7629..5909e10bc 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -65,6 +65,7 @@ bytes = "1" clap = "4" color-eyre = "0.6" config = "0.13.3" +console-subscriber = "0.2.0" convert_case = "0.6" cosmrs = { version = "0.14", default-features = false, features = [ "cosmwasm", @@ -170,11 +171,12 @@ tendermint-rpc = { version = "0.32.0", features = ["http-client", "tokio"] } thiserror = "1.0" time = "0.3" tiny-keccak = "2.0.2" -tokio = { version = "1", features = ["parking_lot"] } +tokio = { version = "1", features = ["parking_lot", "tracing"] } +tokio-metrics = { version = "0.3.1", default-features = false } tokio-test = "0.4" toml_edit = "0.19.14" tonic = "0.9.2" -tracing = { version = "0.1", features = ["release_max_level_debug"] } +tracing = { version = "0.1" } tracing-error = "0.2" tracing-futures = "0.2" tracing-subscriber = { version = "0.3", default-features = false } diff --git a/rust/Dockerfile b/rust/Dockerfile index f931c0fa7..7a5c88260 100644 --- a/rust/Dockerfile +++ b/rust/Dockerfile @@ -27,7 +27,7 @@ RUN \ --mount=id=cargo,type=cache,sharing=locked,target=/usr/src/target \ --mount=id=cargo-home-registry,type=cache,sharing=locked,target=/usr/local/cargo/registry \ --mount=id=cargo-home-git,type=cache,sharing=locked,target=/usr/local/cargo/git \ - cargo build --release --bin validator --bin relayer --bin scraper && \ + RUSTFLAGS="--cfg tokio_unstable" cargo build --release --bin validator --bin relayer --bin scraper && \ mkdir -p /release && \ cp /usr/src/target/release/validator /release && \ cp /usr/src/target/release/relayer /release && \ diff --git a/rust/agents/relayer/.cargo/config.toml b/rust/agents/relayer/.cargo/config.toml new file mode 100644 index 000000000..bff29e6e1 --- /dev/null +++ b/rust/agents/relayer/.cargo/config.toml @@ -0,0 +1,2 @@ +[build] +rustflags = ["--cfg", "tokio_unstable"] diff --git a/rust/agents/relayer/Cargo.toml b/rust/agents/relayer/Cargo.toml index 59b1c74d6..2df8f54d9 100644 --- a/rust/agents/relayer/Cargo.toml +++ b/rust/agents/relayer/Cargo.toml @@ -13,6 +13,7 @@ version.workspace = true async-trait.workspace = true axum.workspace = true config.workspace = true +console-subscriber.workspace = true convert_case.workspace = true derive-new.workspace = true derive_more.workspace = true @@ -32,6 +33,7 @@ serde_json.workspace = true strum.workspace = true thiserror.workspace = true tokio = { workspace = true, features = ["rt", "macros", "parking_lot", "rt-multi-thread"] } +tokio-metrics.workspace = true tracing-futures.workspace = true tracing.workspace = true @@ -41,6 +43,7 @@ hyperlane-ethereum = { path = "../../chains/hyperlane-ethereum" } [dev-dependencies] once_cell.workspace = true +mockall.worksapce = 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/msg/op_submitter.rs b/rust/agents/relayer/src/msg/op_submitter.rs index 4350baeff..dc3091149 100644 --- a/rust/agents/relayer/src/msg/op_submitter.rs +++ b/rust/agents/relayer/src/msg/op_submitter.rs @@ -4,10 +4,10 @@ use derive_new::new; use futures::future::join_all; use futures_util::future::try_join_all; use prometheus::{IntCounter, IntGaugeVec}; -use tokio::spawn; use tokio::sync::mpsc; use tokio::task::JoinHandle; use tokio::time::sleep; +use tokio_metrics::TaskMonitor; use tracing::{debug, info_span, instrument, instrument::Instrumented, trace, Instrument}; use tracing::{info, warn}; @@ -82,12 +82,18 @@ pub struct SerialSubmitter { metrics: SerialSubmitterMetrics, /// Max batch size for submitting messages max_batch_size: u32, + /// tokio task monitor + task_monitor: TaskMonitor, } impl SerialSubmitter { pub fn spawn(self) -> Instrumented> { let span = info_span!("SerialSubmitter", destination=%self.domain); - spawn(async move { self.run().await }).instrument(span) + let task_monitor = self.task_monitor.clone(); + tokio::spawn(TaskMonitor::instrument(&task_monitor, async move { + self.run().await + })) + .instrument(span) } async fn run(self) { @@ -97,6 +103,7 @@ impl SerialSubmitter { rx: rx_prepare, retry_rx, max_batch_size, + task_monitor, } = self; let prepare_queue = OpQueue::new( metrics.submitter_queue_length.clone(), @@ -115,32 +122,40 @@ impl SerialSubmitter { ); let tasks = [ - spawn(receive_task( - domain.clone(), - rx_prepare, - prepare_queue.clone(), + tokio::spawn(TaskMonitor::instrument( + &task_monitor, + receive_task(domain.clone(), rx_prepare, prepare_queue.clone()), )), - spawn(prepare_task( - domain.clone(), - prepare_queue.clone(), - submit_queue.clone(), - confirm_queue.clone(), - max_batch_size, - metrics.clone(), + tokio::spawn(TaskMonitor::instrument( + &task_monitor, + prepare_task( + domain.clone(), + prepare_queue.clone(), + submit_queue.clone(), + confirm_queue.clone(), + max_batch_size, + metrics.clone(), + ), )), - spawn(submit_task( - domain.clone(), - submit_queue, - confirm_queue.clone(), - max_batch_size, - metrics.clone(), + tokio::spawn(TaskMonitor::instrument( + &task_monitor, + submit_task( + domain.clone(), + submit_queue, + confirm_queue.clone(), + max_batch_size, + metrics.clone(), + ), )), - spawn(confirm_task( - domain.clone(), - prepare_queue, - confirm_queue, - max_batch_size, - metrics, + tokio::spawn(TaskMonitor::instrument( + &task_monitor, + confirm_task( + domain.clone(), + prepare_queue, + confirm_queue, + max_batch_size, + metrics, + ), )), ]; diff --git a/rust/agents/relayer/src/msg/processor.rs b/rust/agents/relayer/src/msg/processor.rs index 3aae0d308..166ee6561 100644 --- a/rust/agents/relayer/src/msg/processor.rs +++ b/rust/agents/relayer/src/msg/processor.rs @@ -1,4 +1,5 @@ use std::{ + cmp::max, collections::HashMap, fmt::{Debug, Formatter}, sync::Arc, @@ -8,11 +9,14 @@ use std::{ use async_trait::async_trait; use derive_new::new; use eyre::Result; -use hyperlane_base::{db::HyperlaneRocksDB, CoreMetrics}; +use hyperlane_base::{ + db::{HyperlaneRocksDB, ProcessMessage}, + CoreMetrics, +}; use hyperlane_core::{HyperlaneDomain, HyperlaneMessage}; use prometheus::IntGauge; use tokio::sync::mpsc::UnboundedSender; -use tracing::{debug, trace}; +use tracing::{debug, instrument, trace}; use super::{metadata::AppContextClassifier, op_queue::QueueOperation, pending_message::*}; use crate::{processor::ProcessorExt, settings::matching_list::MatchingList}; @@ -20,9 +24,7 @@ use crate::{processor::ProcessorExt, settings::matching_list::MatchingList}; /// Finds unprocessed messages from an origin and submits then through a channel /// for to the appropriate destination. #[allow(clippy::too_many_arguments)] -#[derive(new)] pub struct MessageProcessor { - db: HyperlaneRocksDB, whitelist: Arc, blacklist: Arc, metrics: MessageProcessorMetrics, @@ -32,16 +34,187 @@ pub struct MessageProcessor { /// Needed context to send a message for each destination chain destination_ctxs: HashMap>, metric_app_contexts: Vec<(MatchingList, String)>, - #[new(default)] - message_nonce: u32, + nonce_iterator: ForwardBackwardIterator, +} + +#[derive(Debug)] +struct ForwardBackwardIterator { + low_nonce_iter: DirectionalNonceIterator, + high_nonce_iter: DirectionalNonceIterator, + // here for debugging purposes + _domain: String, +} + +impl ForwardBackwardIterator { + #[instrument(skip(db), ret)] + fn new(db: Arc) -> Self { + let high_nonce = db.retrieve_highest_seen_message_nonce().ok().flatten(); + let domain = db.domain().name().to_owned(); + let high_nonce_iter = DirectionalNonceIterator::new( + // If the high nonce is None, we start from the beginning + high_nonce.unwrap_or_default().into(), + NonceDirection::High, + db.clone(), + domain.clone(), + ); + let mut low_nonce_iter = + DirectionalNonceIterator::new(high_nonce, NonceDirection::Low, db, domain.clone()); + // Decrement the low nonce to avoid processing the same message twice, which causes double counts in metrics + low_nonce_iter.iterate(); + debug!( + ?low_nonce_iter, + ?high_nonce_iter, + ?domain, + "Initialized ForwardBackwardIterator" + ); + Self { + low_nonce_iter, + high_nonce_iter, + _domain: domain, + } + } + + async fn try_get_next_message( + &mut self, + metrics: &MessageProcessorMetrics, + ) -> Result> { + loop { + let high_nonce_message_status = self.high_nonce_iter.try_get_next_nonce(metrics)?; + let low_nonce_message_status = self.low_nonce_iter.try_get_next_nonce(metrics)?; + // Always prioritize the high nonce message + match (high_nonce_message_status, low_nonce_message_status) { + // Keep iterating if only processed messages are found + (MessageStatus::Processed, _) => { + self.high_nonce_iter.iterate(); + } + (_, MessageStatus::Processed) => { + self.low_nonce_iter.iterate(); + } + // Otherwise return - either a processable message or nothing to process + (MessageStatus::Processable(high_nonce_message), _) => { + self.high_nonce_iter.iterate(); + return Ok(Some(high_nonce_message)); + } + (_, MessageStatus::Processable(low_nonce_message)) => { + self.low_nonce_iter.iterate(); + return Ok(Some(low_nonce_message)); + } + (MessageStatus::Unindexed, MessageStatus::Unindexed) => return Ok(None), + } + // This loop may iterate through millions of processed messages, blocking the runtime. + // So, to avoid starving other futures in this task, yield to the runtime + // on each iteration + tokio::task::yield_now().await; + } + } +} + +#[derive(Debug, Clone, Copy, Default, PartialEq)] +enum NonceDirection { + #[default] + High, + Low, +} + +#[derive(new)] +struct DirectionalNonceIterator { + nonce: Option, + direction: NonceDirection, + db: Arc, + domain_name: String, +} + +impl Debug for DirectionalNonceIterator { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "DirectionalNonceIterator {{ nonce: {:?}, direction: {:?}, domain: {:?} }}", + self.nonce, self.direction, self.domain_name + ) + } +} + +impl DirectionalNonceIterator { + #[instrument] + fn iterate(&mut self) { + match self.direction { + NonceDirection::High => self.nonce = self.nonce.map(|n| n.saturating_add(1)), + NonceDirection::Low => { + if let Some(nonce) = self.nonce { + // once the message with nonce zero is processed, we should stop going backwards + self.nonce = nonce.checked_sub(1); + } + } + } + } + + fn try_get_next_nonce( + &mut self, + metrics: &MessageProcessorMetrics, + ) -> Result> { + if let Some(message) = self.indexed_message_with_nonce()? { + Self::update_max_nonce_gauge(&message, metrics); + if !self.is_message_processed()? { + return Ok(MessageStatus::Processable(message)); + } else { + return Ok(MessageStatus::Processed); + } + } + Ok(MessageStatus::Unindexed) + } + + fn update_max_nonce_gauge(message: &HyperlaneMessage, metrics: &MessageProcessorMetrics) { + let current_max = metrics.max_last_known_message_nonce_gauge.get(); + metrics + .max_last_known_message_nonce_gauge + .set(max(current_max, message.nonce as i64)); + if let Some(metrics) = metrics.get(message.destination) { + metrics.set(message.nonce as i64); + } + } + + fn indexed_message_with_nonce(&self) -> Result> { + match self.nonce { + Some(nonce) => { + let msg = self.db.retrieve_message_by_nonce(nonce)?; + Ok(msg) + } + None => Ok(None), + } + } + + fn is_message_processed(&self) -> Result { + let Some(nonce) = self.nonce else { + return Ok(false); + }; + let processed = self.db.retrieve_processed_by_nonce(nonce)?.unwrap_or(false); + if processed { + trace!( + nonce, + domain = self.db.domain().name(), + "Message already marked as processed in DB" + ); + } + Ok(processed) + } +} + +#[derive(Debug)] +enum MessageStatus { + /// The message wasn't indexed yet so can't be processed. + Unindexed, + // The message was indexed and is ready to be processed. + Processable(T), + // The message was indexed and already processed. + Processed, } impl Debug for MessageProcessor { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!( f, - "MessageProcessor {{ whitelist: {:?}, blacklist: {:?}, message_nonce: {:?} }}", - self.whitelist, self.blacklist, self.message_nonce + "MessageProcessor {{ whitelist: {:?}, blacklist: {:?}, nonce_iterator: {:?}}}", + self.whitelist, self.blacklist, self.nonce_iterator ) } } @@ -50,7 +223,7 @@ impl Debug for MessageProcessor { impl ProcessorExt for MessageProcessor { /// The domain this processor is getting messages from. fn domain(&self) -> &HyperlaneDomain { - self.db.domain() + self.nonce_iterator.high_nonce_iter.db.domain() } /// One round of processing, extracted from infinite work loop for @@ -61,35 +234,31 @@ impl ProcessorExt for MessageProcessor { // self.tx_msg and then continue the scan at the next highest // nonce. // Scan until we find next nonce without delivery confirmation. - if let Some(msg) = self.try_get_unprocessed_message()? { + if let Some(msg) = self.try_get_unprocessed_message().await? { debug!(?msg, "Processor working on message"); let destination = msg.destination; // Skip if not whitelisted. if !self.whitelist.msg_matches(&msg, true) { debug!(?msg, whitelist=?self.whitelist, "Message not whitelisted, skipping"); - self.message_nonce += 1; return Ok(()); } // Skip if the message is blacklisted if self.blacklist.msg_matches(&msg, false) { debug!(?msg, blacklist=?self.blacklist, "Message blacklisted, skipping"); - self.message_nonce += 1; return Ok(()); } // Skip if the message is intended for this origin if destination == self.domain().id() { debug!(?msg, "Message destined for self, skipping"); - self.message_nonce += 1; return Ok(()); } // Skip if the message is intended for a destination we do not service if !self.send_channels.contains_key(&destination) { debug!(?msg, "Message destined for unknown domain, skipping"); - self.message_nonce += 1; return Ok(()); } @@ -106,7 +275,6 @@ impl ProcessorExt for MessageProcessor { app_context, ); self.send_channels[&destination].send(Box::new(pending_msg) as QueueOperation)?; - self.message_nonce += 1; } else { tokio::time::sleep(Duration::from_secs(1)).await; } @@ -115,34 +283,36 @@ impl ProcessorExt for MessageProcessor { } impl MessageProcessor { - fn try_get_unprocessed_message(&mut self) -> Result> { - loop { - // First, see if we can find the message so we can update the gauge. - if let Some(message) = self.db.retrieve_message_by_nonce(self.message_nonce)? { - // Update the latest nonce gauges - self.metrics - .max_last_known_message_nonce_gauge - .set(message.nonce as i64); - if let Some(metrics) = self.metrics.get(message.destination) { - metrics.set(message.nonce as i64); - } + pub fn new( + db: HyperlaneRocksDB, + whitelist: Arc, + blacklist: Arc, + metrics: MessageProcessorMetrics, + send_channels: HashMap>, + destination_ctxs: HashMap>, + metric_app_contexts: Vec<(MatchingList, String)>, + ) -> Self { + Self { + whitelist, + blacklist, + metrics, + send_channels, + destination_ctxs, + metric_app_contexts, + nonce_iterator: ForwardBackwardIterator::new(Arc::new(db) as Arc), + } + } - // If this message has already been processed, on to the next one. - if !self - .db - .retrieve_processed_by_nonce(&self.message_nonce)? - .unwrap_or(false) - { - return Ok(Some(message)); - } else { - debug!(nonce=?self.message_nonce, "Message already marked as processed in DB"); - self.message_nonce += 1; - } - } else { - trace!(nonce=?self.message_nonce, "No message found in DB for nonce"); - return Ok(None); - } + async fn try_get_unprocessed_message(&mut self) -> Result> { + trace!(nonce_iterator=?self.nonce_iterator, "Trying to get the next processor message"); + let next_message = self + .nonce_iterator + .try_get_next_message(&self.metrics) + .await?; + if next_message.is_none() { + trace!(nonce_iterator=?self.nonce_iterator, "No message found in DB for nonce"); } + Ok(next_message) } } @@ -197,7 +367,7 @@ mod test { use super::*; use hyperlane_base::{ - db::{test_utils, HyperlaneRocksDB}, + db::{test_utils, DbResult, HyperlaneRocksDB}, settings::{ChainConf, ChainConnectionConf, Settings}, }; use hyperlane_test::mocks::{MockMailboxContract, MockValidatorAnnounceContract}; @@ -209,6 +379,7 @@ mod test { }, time::sleep, }; + use tokio_metrics::TaskMonitor; fn dummy_processor_metrics(domain_id: u32) -> MessageProcessorMetrics { MessageProcessorMetrics { @@ -367,7 +538,7 @@ mod test { let (message_processor, mut receive_channel) = dummy_message_processor(origin_domain, destination_domain, db); - let processor = Processor::new(Box::new(message_processor)); + let processor = Processor::new(Box::new(message_processor), TaskMonitor::new()); let process_fut = processor.spawn(); let mut pending_messages = vec![]; let pending_message_accumulator = async { @@ -386,6 +557,21 @@ mod test { pending_messages } + mockall::mock! { + pub Db {} + + impl Debug for Db { + fn fmt<'a>(&self, f: &mut std::fmt::Formatter<'a>) -> std::fmt::Result; + } + + impl ProcessMessage for Db { + fn retrieve_highest_seen_message_nonce(&self) -> DbResult>; + fn retrieve_message_by_nonce(&self, nonce: u32) -> DbResult>; + fn retrieve_processed_by_nonce(&self, nonce: u32) -> DbResult>; + fn domain(&self) -> &HyperlaneDomain; + } + } + #[tokio::test] async fn test_full_pending_message_persistence_flow() { test_utils::run_test_db(|db| async move { @@ -440,4 +626,77 @@ mod test { }) .await; } + + #[tokio::test] + async fn test_forward_backward_iterator() { + let mut mock_db = MockDb::new(); + const MAX_ONCHAIN_NONCE: u32 = 4; + const MOCK_HIGHEST_SEEN_NONCE: u32 = 2; + + // How many times the db was queried for the max onchain nonce message + let mut retrieve_calls_for_max_onchain_nonce = 0; + + mock_db + .expect_domain() + .return_const(dummy_domain(0, "dummy_domain")); + mock_db + .expect_retrieve_highest_seen_message_nonce() + .returning(|| Ok(Some(MOCK_HIGHEST_SEEN_NONCE))); + mock_db + .expect_retrieve_message_by_nonce() + .returning(move |nonce| { + // return `None` the first time we get a query for the last message + // (the `MAX_ONCHAIN_NONCE`th one), to simulate an ongoing indexing that hasn't finished + if nonce == MAX_ONCHAIN_NONCE && retrieve_calls_for_max_onchain_nonce == 0 { + retrieve_calls_for_max_onchain_nonce += 1; + return Ok(None); + } + + // otherwise return a message for every nonce in the closed + // interval [0, MAX_ONCHAIN_NONCE] + if nonce > MAX_ONCHAIN_NONCE { + Ok(None) + } else { + Ok(Some(dummy_hyperlane_message( + &dummy_domain(1, "dummy_domain"), + nonce, + ))) + } + }); + + // The messages must be marked as "not processed" in the db for them to be returned + // when the iterator queries them + mock_db + .expect_retrieve_processed_by_nonce() + .returning(|_| Ok(Some(false))); + let dummy_metrics = dummy_processor_metrics(0); + let db = Arc::new(mock_db); + + let mut forward_backward_iterator = ForwardBackwardIterator::new(db.clone()); + + let mut messages = vec![]; + while let Some(msg) = forward_backward_iterator + .try_get_next_message(&dummy_metrics) + .await + .unwrap() + { + messages.push(msg.nonce); + } + + // we start with 2 (MOCK_HIGHEST_SEEN_NONCE) as the highest seen nonce, + // so we go forward and get 3. + // then we try going forward again but get a `None` (not indexed yet), for nonce 4 (MAX_ONCHAIN_NONCE). + // then we go backwards once and get 1. + // then retry the forward iteration, which should return a message the second time, for nonce 4. + // finally, going forward again returns None so we go backward and get 0. + assert_eq!(messages, vec![2, 3, 1, 4, 0]); + + // the final bounds of the iterator are (None, MAX_ONCHAIN_NONCE + 1), where None means + // the backward iterator has reached the beginning (iterated past nonce 0) + assert_eq!(forward_backward_iterator.low_nonce_iter.nonce, None); + assert_eq!( + forward_backward_iterator.high_nonce_iter.nonce, + Some(MAX_ONCHAIN_NONCE + 1) + ); + } } diff --git a/rust/agents/relayer/src/processor.rs b/rust/agents/relayer/src/processor.rs index 56dbe3eb8..c38449cae 100644 --- a/rust/agents/relayer/src/processor.rs +++ b/rust/agents/relayer/src/processor.rs @@ -5,6 +5,7 @@ use derive_new::new; use eyre::Result; use hyperlane_core::HyperlaneDomain; use tokio::task::JoinHandle; +use tokio_metrics::TaskMonitor; use tracing::{instrument, warn}; #[async_trait] @@ -20,11 +21,15 @@ pub trait ProcessorExt: Send + Debug { #[derive(new)] pub struct Processor { ticker: Box, + task_monitor: TaskMonitor, } impl Processor { pub fn spawn(self) -> JoinHandle<()> { - tokio::spawn(async move { self.main_loop().await }) + let task_monitor = self.task_monitor.clone(); + tokio::spawn(TaskMonitor::instrument(&task_monitor, async move { + self.main_loop().await + })) } #[instrument(ret, skip(self), level = "info", fields(domain=%self.ticker.domain()))] diff --git a/rust/agents/relayer/src/relayer.rs b/rust/agents/relayer/src/relayer.rs index 581620f2f..0496e38ca 100644 --- a/rust/agents/relayer/src/relayer.rs +++ b/rust/agents/relayer/src/relayer.rs @@ -25,7 +25,8 @@ use tokio::{ }, task::JoinHandle, }; -use tracing::{info, info_span, instrument::Instrumented, warn, Instrument}; +use tokio_metrics::TaskMonitor; +use tracing::{error, info, info_span, instrument::Instrumented, warn, Instrument}; use crate::{ merkle_tree::builder::MerkleTreeBuilder, @@ -79,6 +80,8 @@ pub struct Relayer { // or move them in `core_metrics`, like the validator metrics agent_metrics: AgentMetrics, chain_metrics: ChainMetrics, + /// Tokio console server + pub tokio_console_server: Option, } impl Debug for Relayer { @@ -109,6 +112,7 @@ impl BaseAgent for Relayer { core_metrics: Arc, agent_metrics: AgentMetrics, chain_metrics: ChainMetrics, + tokio_console_server: console_subscriber::Server, ) -> Result where Self: Sized, @@ -280,13 +284,26 @@ impl BaseAgent for Relayer { core_metrics, agent_metrics, chain_metrics, + tokio_console_server: Some(tokio_console_server), }) } #[allow(clippy::async_yields_async)] - async fn run(self) { + async fn run(mut self) { let mut tasks = vec![]; + let task_monitor = tokio_metrics::TaskMonitor::new(); + if let Some(tokio_console_server) = self.tokio_console_server.take() { + let console_server = + tokio::spawn(TaskMonitor::instrument(&task_monitor.clone(), async move { + info!("Starting tokio console server"); + if let Err(e) = tokio_console_server.serve().await { + error!(error=?e, "Tokio console server failed to start"); + } + })); + tasks.push(console_server.instrument(info_span!("Tokio console server"))); + } + // run server let mpmc_channel = MpmcChannel::::new(ENDPOINT_MESSAGES_QUEUE_SIZE); let custom_routes = relayer_server::routes(mpmc_channel.sender()); @@ -318,6 +335,7 @@ impl BaseAgent for Relayer { .operation_batch_config() .map(|c| c.max_batch_size) .unwrap_or(1), + task_monitor.clone(), ), ); @@ -334,15 +352,25 @@ impl BaseAgent for Relayer { } for origin in &self.origin_chains { - tasks.push(self.run_message_sync(origin).await); - tasks.push(self.run_interchain_gas_payment_sync(origin).await); - tasks.push(self.run_merkle_tree_hook_syncs(origin).await); + tasks.push(self.run_message_sync(origin, task_monitor.clone()).await); + tasks.push( + self.run_interchain_gas_payment_sync(origin, task_monitor.clone()) + .await, + ); + tasks.push( + self.run_merkle_tree_hook_syncs(origin, task_monitor.clone()) + .await, + ); } // each message process attempts to send messages from a chain for origin in &self.origin_chains { - tasks.push(self.run_message_processor(origin, send_channels.clone())); - tasks.push(self.run_merkle_tree_processor(origin)); + tasks.push(self.run_message_processor( + origin, + send_channels.clone(), + task_monitor.clone(), + )); + tasks.push(self.run_merkle_tree_processor(origin, task_monitor.clone())); } if let Err(err) = try_join_all(tasks).await { @@ -355,22 +383,27 @@ impl BaseAgent for Relayer { } impl Relayer { - async fn run_message_sync(&self, origin: &HyperlaneDomain) -> Instrumented> { + async fn run_message_sync( + &self, + origin: &HyperlaneDomain, + task_monitor: TaskMonitor, + ) -> 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.cursor(index_settings).await; - tokio::spawn(async move { + tokio::spawn(TaskMonitor::instrument(&task_monitor, async move { contract_sync .clone() .sync("dispatched_messages", cursor) .await - }) + })) .instrument(info_span!("MessageSync")) } async fn run_interchain_gas_payment_sync( &self, origin: &HyperlaneDomain, + task_monitor: TaskMonitor, ) -> Instrumented> { let index_settings = self.as_ref().settings.chains[origin.name()].index_settings(); let contract_sync = self @@ -379,25 +412,31 @@ impl Relayer { .unwrap() .clone(); let cursor = contract_sync.cursor(index_settings).await; - tokio::spawn(async move { contract_sync.clone().sync("gas_payments", cursor).await }) - .instrument(info_span!("IgpSync")) + tokio::spawn(TaskMonitor::instrument(&task_monitor, async move { + contract_sync.clone().sync("gas_payments", cursor).await + })) + .instrument(info_span!("IgpSync")) } async fn run_merkle_tree_hook_syncs( &self, origin: &HyperlaneDomain, + task_monitor: TaskMonitor, ) -> 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.cursor(index_settings).await; - tokio::spawn(async move { contract_sync.clone().sync("merkle_tree_hook", cursor).await }) - .instrument(info_span!("MerkleTreeHookSync")) + tokio::spawn(TaskMonitor::instrument(&task_monitor, async move { + contract_sync.clone().sync("merkle_tree_hook", cursor).await + })) + .instrument(info_span!("MerkleTreeHookSync")) } fn run_message_processor( &self, origin: &HyperlaneDomain, send_channels: HashMap>, + task_monitor: TaskMonitor, ) -> Instrumented> { let metrics = MessageProcessorMetrics::new( &self.core.metrics, @@ -431,12 +470,16 @@ impl Relayer { ); let span = info_span!("MessageProcessor", origin=%message_processor.domain()); - let processor = Processor::new(Box::new(message_processor)); + let processor = Processor::new(Box::new(message_processor), task_monitor.clone()); processor.spawn().instrument(span) } - fn run_merkle_tree_processor(&self, origin: &HyperlaneDomain) -> Instrumented> { + fn run_merkle_tree_processor( + &self, + origin: &HyperlaneDomain, + task_monitor: TaskMonitor, + ) -> Instrumented> { let metrics = MerkleTreeProcessorMetrics::new(); let merkle_tree_processor = MerkleTreeProcessor::new( self.dbs.get(origin).unwrap().clone(), @@ -445,7 +488,7 @@ impl Relayer { ); let span = info_span!("MerkleTreeProcessor", origin=%merkle_tree_processor.domain()); - let processor = Processor::new(Box::new(merkle_tree_processor)); + let processor = Processor::new(Box::new(merkle_tree_processor), task_monitor.clone()); processor.spawn().instrument(span) } @@ -457,6 +500,7 @@ impl Relayer { receiver: UnboundedReceiver, retry_receiver_channel: MpmcReceiver, batch_size: u32, + task_monitor: TaskMonitor, ) -> Instrumented> { let serial_submitter = SerialSubmitter::new( destination.clone(), @@ -464,10 +508,11 @@ impl Relayer { retry_receiver_channel, SerialSubmitterMetrics::new(&self.core.metrics, destination), batch_size, + task_monitor.clone(), ); let span = info_span!("SerialSubmitter", destination=%destination); let destination = destination.clone(); - tokio::spawn(async move { + tokio::spawn(TaskMonitor::instrument(&task_monitor, async move { // Propagate task panics serial_submitter.spawn().await.unwrap_or_else(|err| { panic!( @@ -475,7 +520,7 @@ impl Relayer { 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 da037f19b..097855f17 100644 --- a/rust/agents/relayer/src/settings/matching_list.rs +++ b/rust/agents/relayer/src/settings/matching_list.rs @@ -1,4 +1,4 @@ -//! The correct settings shape is defined in the TypeScript SDK metadata. While the the exact shape +//! The correct settings shape is defined in the TypeScript SDK metadata. While the exact shape //! and validations it defines are not applied here, we should mirror them. //! ANY CHANGES HERE NEED TO BE REFLECTED IN THE TYPESCRIPT SDK. @@ -267,13 +267,13 @@ impl<'a> From<&'a HyperlaneMessage> for MatchInfo<'a> { impl MatchingList { /// Check if a message matches any of the rules. - /// - `default`: What to return if the the matching list is empty. + /// - `default`: What to return if the matching list is empty. pub fn msg_matches(&self, msg: &HyperlaneMessage, default: bool) -> bool { self.matches(msg.into(), default) } /// Check if a message matches any of the rules. - /// - `default`: What to return if the the matching list is empty. + /// - `default`: What to return if the matching list is empty. fn matches(&self, info: MatchInfo, default: bool) -> bool { if let Some(rules) = &self.0 { matches_any_rule(rules.iter(), info) diff --git a/rust/agents/relayer/src/settings/mod.rs b/rust/agents/relayer/src/settings/mod.rs index c6934a77c..2e6b00573 100644 --- a/rust/agents/relayer/src/settings/mod.rs +++ b/rust/agents/relayer/src/settings/mod.rs @@ -1,6 +1,6 @@ //! Relayer configuration //! -//! The correct settings shape is defined in the TypeScript SDK metadata. While the the exact shape +//! The correct settings shape is defined in the TypeScript SDK metadata. While the exact shape //! and validations it defines are not applied here, we should mirror them. //! ANY CHANGES HERE NEED TO BE REFLECTED IN THE TYPESCRIPT SDK. diff --git a/rust/agents/scraper/Cargo.toml b/rust/agents/scraper/Cargo.toml index 587a56b8d..234813573 100644 --- a/rust/agents/scraper/Cargo.toml +++ b/rust/agents/scraper/Cargo.toml @@ -12,6 +12,7 @@ version.workspace = true [dependencies] async-trait.workspace = true config.workspace = true +console-subscriber.workspace = true derive_more.workspace = true ethers.workspace = true eyre.workspace = true diff --git a/rust/agents/scraper/src/agent.rs b/rust/agents/scraper/src/agent.rs index cc113cacf..d71343281 100644 --- a/rust/agents/scraper/src/agent.rs +++ b/rust/agents/scraper/src/agent.rs @@ -44,6 +44,7 @@ impl BaseAgent for Scraper { metrics: Arc, agent_metrics: AgentMetrics, chain_metrics: ChainMetrics, + _tokio_console_server: console_subscriber::Server, ) -> eyre::Result where Self: Sized, diff --git a/rust/agents/scraper/src/settings.rs b/rust/agents/scraper/src/settings.rs index b4bdfbb4d..b6c9eb7e4 100644 --- a/rust/agents/scraper/src/settings.rs +++ b/rust/agents/scraper/src/settings.rs @@ -1,6 +1,6 @@ //! Scraper configuration. //! -//! The correct settings shape is defined in the TypeScript SDK metadata. While the the exact shape +//! The correct settings shape is defined in the TypeScript SDK metadata. While the exact shape //! and validations it defines are not applied here, we should mirror them. //! ANY CHANGES HERE NEED TO BE REFLECTED IN THE TYPESCRIPT SDK. diff --git a/rust/agents/validator/Cargo.toml b/rust/agents/validator/Cargo.toml index 98a5fe6f8..e9e66eb30 100644 --- a/rust/agents/validator/Cargo.toml +++ b/rust/agents/validator/Cargo.toml @@ -13,6 +13,7 @@ version.workspace = true async-trait.workspace = true axum.workspace = true config.workspace = true +console-subscriber.workspace = true derive_more.workspace = true derive-new.workspace = true ethers.workspace = true diff --git a/rust/agents/validator/src/settings.rs b/rust/agents/validator/src/settings.rs index f6870a31c..70158d267 100644 --- a/rust/agents/validator/src/settings.rs +++ b/rust/agents/validator/src/settings.rs @@ -1,6 +1,6 @@ //! Validator configuration. //! -//! The correct settings shape is defined in the TypeScript SDK metadata. While the the exact shape +//! The correct settings shape is defined in the TypeScript SDK metadata. While the exact shape //! and validations it defines are not applied here, we should mirror them. //! ANY CHANGES HERE NEED TO BE REFLECTED IN THE TYPESCRIPT SDK. diff --git a/rust/agents/validator/src/validator.rs b/rust/agents/validator/src/validator.rs index 8d2898009..043ac9249 100644 --- a/rust/agents/validator/src/validator.rs +++ b/rust/agents/validator/src/validator.rs @@ -63,6 +63,7 @@ impl BaseAgent for Validator { metrics: Arc, agent_metrics: AgentMetrics, chain_metrics: ChainMetrics, + _tokio_console_server: console_subscriber::Server, ) -> Result where Self: Sized, diff --git a/rust/chains/hyperlane-ethereum/src/contracts/mailbox.rs b/rust/chains/hyperlane-ethereum/src/contracts/mailbox.rs index fd6a6b280..d70c2bfc7 100644 --- a/rust/chains/hyperlane-ethereum/src/contracts/mailbox.rs +++ b/rust/chains/hyperlane-ethereum/src/contracts/mailbox.rs @@ -164,7 +164,7 @@ impl SequenceAwareIndexer for EthereumMailboxIndexer where M: Middleware + 'static, { - #[instrument(err, skip(self))] + #[instrument(err, skip(self), ret)] 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?; diff --git a/rust/config/mainnet_config.json b/rust/config/mainnet_config.json index e7dd84b78..25bfbb895 100644 --- a/rust/config/mainnet_config.json +++ b/rust/config/mainnet_config.json @@ -1,6 +1,7 @@ { "chains": { "ancient8": { + "aggregationHook": "0x1EF4ED658d542524d1D547ba2F94d3B038a55b8f", "batchContractAddress": "0x4C97D35c668EE5194a13c8DE8Afc18cce40C9F28", "blockExplorers": [ { @@ -18,6 +19,7 @@ "chainId": 888888888, "displayName": "Ancient8", "domainId": 888888888, + "domainRoutingIsm": "0xB6F0f1267B01C27326F61a4B4fe2c73751802685", "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0x5E01d8F34b629E3f92d69546bbc4142A7Adee7e9", "gasCurrencyCoinGeckoId": "ethereum", @@ -25,7 +27,7 @@ "from": 2507127 }, "interchainGasPaymaster": "0x8F1E22d309baa69D398a03cc88E9b46037e988AA", - "interchainSecurityModule": "0x6E3387e12C6e181BF8e712eCa9c60ccEEaBD1c67", + "interchainSecurityModule": "0xBd3C7253F08c040eDB9c54e7CD4f8a5fd1eb935D", "isTestnet": false, "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0x811808Dd29ba8B0FC6C0ec0b5537035E59745162", @@ -36,6 +38,7 @@ "symbol": "ETH" }, "pausableHook": "0x66DC49405Ae2956f7E87FEAa9fE8f506C8987462", + "pausableIsm": "0xcf678903c003651DB0bb933820259A16ea9d95e4", "protocol": "ethereum", "protocolFee": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", "proxyAdmin": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", @@ -45,6 +48,7 @@ } ], "staticAggregationHookFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", + "staticAggregationIsm": "0xBd3C7253F08c040eDB9c54e7CD4f8a5fd1eb935D", "staticAggregationIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", "staticMerkleRootMultisigIsmFactory": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", "staticMessageIdMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", @@ -71,6 +75,7 @@ "chainId": 42161, "displayName": "Arbitrum", "domainId": 42161, + "domainRoutingIsm": "0x5d759B5CeEb1C3b0181bEc0F80fb04f820cc35D1", "domainRoutingIsmFactory": "0xa2931C37957f3079d3B21b877d56E1db930e02a5", "fallbackRoutingHook": "0x9e8fFb1c26099e75Dd5D794030e2E9AA51471c25", "gasCurrencyCoinGeckoId": "ethereum", @@ -81,7 +86,7 @@ "interchainAccountIsm": "0xfa8bfcE55B3A0631dF38257615cEF7FCD3523A48", "interchainAccountRouter": "0xCD0CFFf6eFD943b4b81f2c15847730dbcD30e3aE", "interchainGasPaymaster": "0x3b6044acd6767f017e99318AA6Ef93b7B06A5a22", - "interchainSecurityModule": "0xD0DBBF922076352cC50B285A0023536561F00EEa", + "interchainSecurityModule": "0x96845a0469363f90779f6D5cd49D79bDDAc69429", "mailbox": "0x979Ca5202784112f4738403dBec5D0F3B9daabB9", "merkleTreeHook": "0x748040afB89B8FdBb992799808215419d36A0930", "name": "arbitrum", @@ -91,15 +96,23 @@ "symbol": "ETH" }, "pausableHook": "0xEf30f29Dcd3FCB1DCcDA9C7Cbf2A5957E8Ee9Cc3", + "pausableIsm": "0x1E38556b4fE553e6249448960875883990efcf34", "protocol": "ethereum", "protocolFee": "0xD0199067DACb8526e7dc524a9a7DCBb57Cd25421", "proxyAdmin": "0x80Cebd56A65e46c474a1A101e89E76C4c51D179c", "rpcUrls": [ + { + "http": "https://arbitrum.llamarpc.com" + }, + { + "http": "https://rpc.ankr.com/arbitrum" + }, { "http": "https://arb1.arbitrum.io/rpc" } ], "staticAggregationHookFactory": "0x9B5f440bBb64Fee337F37e03362b628711Ea09C7", + "staticAggregationIsm": "0x96845a0469363f90779f6D5cd49D79bDDAc69429", "staticAggregationIsmFactory": "0xD4883084389fC1Eeb4dAfB2ADcFc36B711c310EB", "staticMerkleRootMultisigIsmFactory": "0x3C330D4A2e2b8443AFaB8E326E64ab4251B7Eae0", "staticMessageIdMultisigIsmFactory": "0x12Df53079d399a47e9E730df095b712B0FDFA791", @@ -128,6 +141,7 @@ "chainId": 43114, "displayName": "Avalanche", "domainId": 43114, + "domainRoutingIsm": "0x9f68F961ba2dF53b1cB3EbCC0b08e89790C6E2f6", "domainRoutingIsmFactory": "0x28F7907911C7E321c596686AE6D1F20516450037", "fallbackRoutingHook": "0x61D15D571D5f7A9eF0D1938f072f430bBF024747", "gasCurrencyCoinGeckoId": "avalanche-2", @@ -138,7 +152,7 @@ "interchainAccountIsm": "0x786c26C1857032617c215f265509d6E44e44Bfe3", "interchainAccountRouter": "0xA967A6CE0e73fAf672843DECaA372511996E8852", "interchainGasPaymaster": "0x95519ba800BBd0d34eeAE026fEc620AD978176C0", - "interchainSecurityModule": "0xA36B02a83564f52d9244310Ea439ee6F6AfeFb60", + "interchainSecurityModule": "0xe7a61510EA7197281b49e5bdf1798608d5132595", "mailbox": "0xFf06aFcaABaDDd1fb08371f9ccA15D73D51FeBD6", "merkleTreeHook": "0x84eea61D679F42D92145fA052C89900CBAccE95A", "name": "avalanche", @@ -148,10 +162,14 @@ "symbol": "AVAX" }, "pausableHook": "0x239eB860770F1C48ABAC9bE9825d20e3E7c018df", + "pausableIsm": "0xd76080269C641e1adb786b72ae60Ddac3b6b8ed0", "protocol": "ethereum", "protocolFee": "0xEc4AdA26E51f2685279F37C8aE62BeAd8212D597", "proxyAdmin": "0xd7CF8c05fd81b8cA7CfF8E6C49B08a9D63265c9B", "rpcUrls": [ + { + "http": "https://rpc.ankr.com/avalanche" + }, { "http": "https://api.avax.network/ext/bc/C/rpc", "pagination": { @@ -161,6 +179,7 @@ } ], "staticAggregationHookFactory": "0x3bF6Ac986C7Af9A9Ac356C0e99C0041EFd8D96e7", + "staticAggregationIsm": "0xe7a61510EA7197281b49e5bdf1798608d5132595", "staticAggregationIsmFactory": "0xa5E13796eB7d2EDCc88012c8cfF90D69B51FcF9f", "staticMerkleRootMultisigIsmFactory": "0x896cF1D1B66cD211633eDd589fF158E8Cfaf9B54", "staticMessageIdMultisigIsmFactory": "0x8819D653DF5b1FC0DdB32189a2704E471AF8483c", @@ -188,6 +207,7 @@ "chainId": 8453, "displayName": "Base", "domainId": 8453, + "domainRoutingIsm": "0x80C8F6394c0FcF7bAB16ac08b85484361eCe5888", "domainRoutingIsmFactory": "0x7E27456a839BFF31CA642c060a2b68414Cb6e503", "fallbackRoutingHook": "0x4Eb82Ee35b0a1c1d776E3a3B547f9A9bA6FCC9f2", "gasCurrencyCoinGeckoId": "ethereum", @@ -198,7 +218,7 @@ "interchainAccountIsm": "0x861908E6c8F992537F557da5Fb5876836036b347", "interchainAccountRouter": "0xa85F9e4fdA2FFF1c07f2726a630443af3faDF830", "interchainGasPaymaster": "0xc3F23848Ed2e04C0c6d41bd7804fa8f89F940B94", - "interchainSecurityModule": "0x5D1e7D7c5B9e6dDC8439F67F10c578f2A1084f6F", + "interchainSecurityModule": "0x77bE0b5aE400675063Ce2B2B0d692D9341f4b193", "mailbox": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", "merkleTreeHook": "0x19dc38aeae620380430C200a6E990D5Af5480117", "name": "base", @@ -208,6 +228,7 @@ "symbol": "ETH" }, "pausableHook": "0x46fa3A5780e5B90Eaf34BDED554d5353B5ABE9E7", + "pausableIsm": "0x2AF32cF8e3Cf42d221eDa0c843818fA5ee129E27", "protocol": "ethereum", "protocolFee": "0x99ca8c74cE7Cfa9d72A51fbb05F9821f5f826b3a", "proxyAdmin": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", @@ -223,14 +244,17 @@ } ], "staticAggregationHookFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", + "staticAggregationIsm": "0x77bE0b5aE400675063Ce2B2B0d692D9341f4b193", "staticAggregationIsmFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", "staticMerkleRootMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", "staticMessageIdMultisigIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", "storageGasOracle": "0xBF12ef4B9f307463D3FB59c3604F294dDCe287E2", + "testRecipient": "0xb7C9307fE90B9AB093c6D3EdeE3259f5378D5f03", "timelockController": "0x0000000000000000000000000000000000000000", "validatorAnnounce": "0x182E8d7c5F1B06201b102123FC7dF0EaeB445a7B" }, "blast": { + "aggregationHook": "0x012278333Ce0A845AE9bD7302867a59Bd5D3635d", "blockExplorers": [ { "apiUrl": "https://api.routescan.io/v2/network/mainnet/evm/81457/etherscan/api", @@ -247,6 +271,7 @@ "chainId": 81457, "displayName": "Blast", "domainId": 81457, + "domainRoutingIsm": "0x0296D16d371a49F631143612020138896b3eA421", "domainRoutingIsmFactory": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "fallbackRoutingHook": "0x6Fae4D9935E2fcb11fC79a64e917fb2BF14DaFaa", "gasCurrencyCoinGeckoId": "ethereum", @@ -254,7 +279,7 @@ "from": 2496427 }, "interchainGasPaymaster": "0xB3fCcD379ad66CED0c91028520C64226611A48c9", - "interchainSecurityModule": "0x0986f6D82A47045788b0ce8EF68f6C0D77726854", + "interchainSecurityModule": "0x208263bB303B2a737642fB13C765F106a2591be8", "mailbox": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7", "merkleTreeHook": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", "name": "blast", @@ -264,6 +289,7 @@ "symbol": "ETH" }, "pausableHook": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", + "pausableIsm": "0x4C97D35c668EE5194a13c8DE8Afc18cce40C9F28", "protocol": "ethereum", "protocolFee": "0x12582c7B0f43c6A667CBaA7fA8b112F7fb1E69F0", "proxyAdmin": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", @@ -273,6 +299,7 @@ } ], "staticAggregationHookFactory": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", + "staticAggregationIsm": "0x208263bB303B2a737642fB13C765F106a2591be8", "staticAggregationIsmFactory": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", "staticMerkleRootMultisigIsmFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", "staticMessageIdMultisigIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", @@ -300,6 +327,7 @@ "displayName": "Binance Smart Chain", "displayNameShort": "Binance", "domainId": 56, + "domainRoutingIsm": "0xBc3Af0D4930502Ff0f6a8416a7a184c7BFFe19E7", "domainRoutingIsmFactory": "0xe6Af5720d34213C805C08e2470aea979e3F72F75", "fallbackRoutingHook": "0x237E81f87F57Badad9e09f13CC676D986cA852e7", "gasCurrencyCoinGeckoId": "binancecoin", @@ -310,7 +338,7 @@ "interchainAccountIsm": "0xB274Bbbc1df5f1d1763216A93d473fde6f5de043", "interchainAccountRouter": "0x4BBd67dC995572b40Dc6B3eB6CdE5185a5373868", "interchainGasPaymaster": "0x78E25e7f84416e69b9339B0A6336EB6EFfF6b451", - "interchainSecurityModule": "0xab3df354baBee6c2B88E2CeD3b2e030e31aA5e61", + "interchainSecurityModule": "0xfA360ff588623A026BF19A1801F2A8F1f045fa33", "mailbox": "0x2971b9Aec44bE4eb673DF1B88cDB57b96eefe8a4", "merkleTreeHook": "0xFDb9Cd5f9daAA2E4474019405A328a88E7484f26", "name": "bsc", @@ -320,6 +348,7 @@ "symbol": "BNB" }, "pausableHook": "0x7DBdAd1b4A922B65d37d7258a4227b6658344b7f", + "pausableIsm": "0x25dB01caDf91CfD2f7e6dD829Ce81698217F9151", "protocol": "ethereum", "protocolFee": "0xA8Aa5f14a5463a78E45CC068F11c867949F3E367", "proxyAdmin": "0x65993Af9D0D3a64ec77590db7ba362D6eB78eF70", @@ -335,6 +364,7 @@ } ], "staticAggregationHookFactory": "0xe70E86a7D1e001D419D71F960Cb6CaD59b6A3dB6", + "staticAggregationIsm": "0xfA360ff588623A026BF19A1801F2A8F1f045fa33", "staticAggregationIsmFactory": "0x38B3878c4fb44d201DA924c4a04bae3EE728c065", "staticMerkleRootMultisigIsmFactory": "0xfADBc81Ca8A957F1Bf7c78bCc575b28DBDE042b6", "staticMessageIdMultisigIsmFactory": "0x4B1d8352E35e3BDE36dF5ED2e73C24E35c4a96b7", @@ -374,14 +404,14 @@ "domainRoutingIsm": "0xf18E32428dad0802C5D6F723cB80A6Da889777c4", "domainRoutingIsmFactory": "0x2A2c22B0a8615ad24839fA6Af302E896Af32d1a3", "fallbackRoutingHook": "0xDC98a856fb9112894c2fE32267DA8bF35645FAF3", - "gnosisSafeTransactionServiceUrl": "https://mainnet-tx-svc.celo-safe-prod.celo-networks-dev.org/", + "gnosisSafeTransactionServiceUrl": "https://safe-transaction-celo.safe.global/", "index": { "from": 22102340 }, "interchainAccountIsm": "0x30a8DEc5318e2aAa9ad5b069fC606c4CfF6f5676", "interchainAccountRouter": "0x4ED23E3885e1651E62564F78817D91865beba575", "interchainGasPaymaster": "0x571f1435613381208477ac5d6974310d88AC7cB7", - "interchainSecurityModule": "0x99e8E56Dce3402D6E09A82718937fc1cA2A9491E", + "interchainSecurityModule": "0x0dcb01D4ABfa73fadB17C4B0e8cd52A38BD52c66", "mailbox": "0x50da3B3907A08a24fe4999F4Dcf337E8dC7954bb", "merkleTreeHook": "0x04dB778f05854f26E67e0a66b740BBbE9070D366", "name": "celo", @@ -435,8 +465,10 @@ "chainId": 1, "displayName": "Ethereum", "domainId": 1, + "domainRoutingIsm": "0xBA328338044e0C0AFd0591FB6E5e2F83C4e8F742", "domainRoutingIsmFactory": "0x28fA9552F19039b450498B0d8e5DEAe0d0aAc559", "fallbackRoutingHook": "0x571f1435613381208477ac5d6974310d88AC7cB7", + "gasCurrencyCoinGeckoId": "ethereum", "gnosisSafeTransactionServiceUrl": "https://safe-transaction-mainnet.safe.global/", "index": { "from": 18422581 @@ -444,7 +476,7 @@ "interchainAccountIsm": "0x609707355a53d2aAb6366f48E2b607C599D26B29", "interchainAccountRouter": "0x8dBae9B1616c46A20591fE0006Bf015E28ca5cC9", "interchainGasPaymaster": "0x9e6B1022bE9BBF5aFd152483DAD9b88911bC8611", - "interchainSecurityModule": "0xB42b88243F749F47697F01Ae1cbBCA9d4763902a", + "interchainSecurityModule": "0x8CE0c6cAf18DbF5882b35F26E28412f3E9AbDeca", "mailbox": "0xc005dc82818d67AF737725bD4bf75435d065D239", "merkleTreeHook": "0x48e6c30B97748d1e2e03bf3e9FbE3890ca5f8CCA", "name": "ethereum", @@ -454,6 +486,7 @@ "symbol": "ETH" }, "pausableHook": "0x3A66Dc852e56d3748838b3C27CF381105b83705b", + "pausableIsm": "0xDC98a856fb9112894c2fE32267DA8bF35645FAF3", "protocol": "ethereum", "protocolFee": "0x8B05BF30F6247a90006c5837eA63C7905D79e6d8", "proxyAdmin": "0x75EE15Ee1B4A75Fa3e2fDF5DF3253c25599cc659", @@ -466,6 +499,7 @@ } ], "staticAggregationHookFactory": "0x6D2555A8ba483CcF4409C39013F5e9a3285D3C9E", + "staticAggregationIsm": "0x5447cdC0f4B1Afd827BF9d2F6b6cE7668d5dc284", "staticAggregationIsmFactory": "0x46FA191Ad972D9674Ed752B69f9659A0d7b22846", "staticMerkleRootMultisigIsmFactory": "0x47e8aF9e30C32Ab91060ED587894288786761B45", "staticMessageIdMultisigIsmFactory": "0xfA21D9628ADce86531854C2B7ef00F07394B0B69", @@ -497,6 +531,7 @@ "chainId": 100, "displayName": "Gnosis", "domainId": 100, + "domainRoutingIsm": "0x83873DB8B4982091D0781B4eDF108DCb98075C39", "domainRoutingIsmFactory": "0xbB5Df000113e767dE11343A16f83De733e5bCC0F", "fallbackRoutingHook": "0x24f5E353dD03E103Ba2372F7D6FC0cf3A66f849c", "gasCurrencyCoinGeckoId": "xdai", @@ -507,7 +542,7 @@ "interchainAccountIsm": "0x5a56dff3D92D635372718f86e6dF09C1129CFf53", "interchainAccountRouter": "0x5E59EBAedeB691408EBAcF6C37218fa2cFcaC9f2", "interchainGasPaymaster": "0xDd260B99d302f0A3fF885728c086f729c06f227f", - "interchainSecurityModule": "0x8e1aa0687B6d939D5a44304D13B7c922ebB012f1", + "interchainSecurityModule": "0x5DB7edF8C1CF91e34895dB2e4b28d8b9C68ddC7B", "mailbox": "0xaD09d78f4c6b9dA2Ae82b1D34107802d380Bb74f", "merkleTreeHook": "0x2684C6F89E901987E1FdB7649dC5Be0c57C61645", "name": "gnosis", @@ -517,6 +552,7 @@ "symbol": "xDai" }, "pausableHook": "0xf728C884De5275a608dEC222dACd0f2BF2E23AB6", + "pausableIsm": "0x223F7D3f27E6272266AE4B5B91Fd5C7A2d798cD8", "protocol": "ethereum", "protocolFee": "0x9c2214467Daf9e2e1F45b36d08ce0b9C65BFeA88", "proxyAdmin": "0x81a92A1a272cb09d7b4970b07548463dC7aE0cB7", @@ -530,6 +566,7 @@ } ], "staticAggregationHookFactory": "0xbC8AA096dabDf4A0200BB9f8D4Cbb644C3D86d7B", + "staticAggregationIsm": "0xe640167B9a283C8b4039fA33f3ac7be6e7E788c5", "staticAggregationIsmFactory": "0x11EF91d17c5ad3330DbCa709a8841743d3Af6819", "staticMerkleRootMultisigIsmFactory": "0x8E273260EAd8B72A085B19346A676d355740e875", "staticMessageIdMultisigIsmFactory": "0x603f46cc520d2fc22957b81e206408590808F02F", @@ -567,7 +604,7 @@ "interchainAccountIsm": "0x31894E7a734540B343d67E491148EB4FC9f7A45B", "interchainAccountRouter": "0x4E55aDA3ef1942049EA43E904EB01F4A0a9c39bd", "interchainGasPaymaster": "0x19dc38aeae620380430C200a6E990D5Af5480117", - "interchainSecurityModule": "0x3052aD50De54aAAc5D364d80bBE681d29e924597", + "interchainSecurityModule": "0x440f7AD246F3e75df88a6338E8A33e91DA4B2B05", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0x0972954923a1e2b2aAb04Fa0c4a0797e5989Cd65", "name": "inevm", @@ -652,6 +689,7 @@ "domainRoutingIsmFactory": "0x8358D8291e3bEDb04804975eEa0fe9fe0fAfB147", "fallbackRoutingHook": "0xD1E267d2d7876e97E217BfE61c34AB50FEF52807", "gasCurrencyCoinGeckoId": "ethereum", + "gnosisSafeTransactionServiceUrl": "https://transaction.safe.manta.network", "index": { "from": 437300 }, @@ -892,6 +930,39 @@ "timelockController": "0x0000000000000000000000000000000000000000", "validatorAnnounce": "0x30f5b08e01808643221528BB2f7953bf2830Ef38" }, + "osmosis": { + "bech32Prefix": "osmo", + "blocks": { + "reorgPeriod": 1 + }, + "canonicalAsset": "uosmo", + "chainId": "osmosis-1", + "contractAddressBytes": 32, + "domainId": "875", + "gasPrice": { + "amount": "0.025", + "denom": "uosmo" + }, + "grpcUrls": [ + { + "http": "https://osmosis-grpc.publicnode.com:443" + } + ], + "index": { + "from": 14389169 + }, + "interchainGasPaymaster": "0xd20a9dcf61939fc2fe6ad501b9457b1029b3cc7ab12ed72675ea2e10d831ee5d", + "mailbox": "0x9493e39d85dd038022f97d88aba6bff98d98f9a016b4f2e498bf1d9898420172", + "merkleTreeHook": "0x8920e062ee5ed8afccbc155d13ea9049296399ee41403655864fcd243edc7388", + "name": "osmosis1", + "protocol": "cosmos", + "rpcUrls": [ + { + "http": "https://osmosis-rpc.publicnode.com:443" + } + ], + "validatorAnnounce": "0xaf867da5b09a20ee49161d57f99477c0c42d100f34eb53da0d2eb7fc6c257235" + }, "polygon": { "aggregationHook": "0x34dAb05650Cf590088bA18aF9d597f3e081bCc47", "blockExplorers": [ @@ -910,6 +981,7 @@ "chainId": 137, "displayName": "Polygon", "domainId": 137, + "domainRoutingIsm": "0xBcb9d74E1D2549fc1939023433aaAB11587bc338", "domainRoutingIsmFactory": "0x0d0E816eE4557689d34fAd5885C53b9393C1D9fA", "fallbackRoutingHook": "0xca4cCe24E7e06241846F5EA0cda9947F0507C40C", "gasCurrencyCoinGeckoId": "matic-network", @@ -920,7 +992,7 @@ "interchainAccountIsm": "0x90384bC552e3C48af51Ef7D9473A9bF87431f5c7", "interchainAccountRouter": "0x5e80f3474825B61183c0F0f0726796F589082420", "interchainGasPaymaster": "0x0071740Bf129b05C4684abfbBeD248D80971cce2", - "interchainSecurityModule": "0x9a795fB62f86146ec06e2377e3C95Af65c7C20eB", + "interchainSecurityModule": "0xe289bD204Dbb4F3aaFA27Dbe5751C71e101CFD80", "mailbox": "0x5d934f4e2f797775e53561bB72aca21ba36B96BB", "merkleTreeHook": "0x73FbD25c3e817DC4B4Cd9d00eff6D83dcde2DfF6", "name": "polygon", @@ -930,6 +1002,7 @@ "symbol": "ETH" }, "pausableHook": "0x748040afB89B8FdBb992799808215419d36A0930", + "pausableIsm": "0x6741e91fFDC31c7786E3684427c628dad06299B0", "protocol": "ethereum", "protocolFee": "0xF8F3629e308b4758F8396606405989F8D8C9c578", "proxyAdmin": "0xC4F7590C5d30BE959225dC75640657954A86b980", @@ -945,6 +1018,7 @@ } ], "staticAggregationHookFactory": "0xFeeB86e70e4a640cDd29636CCE19BD6fe8628135", + "staticAggregationIsm": "0xe289bD204Dbb4F3aaFA27Dbe5751C71e101CFD80", "staticAggregationIsmFactory": "0x81AdDD9Ca89105063DaDEBd5B4408551Ce850E22", "staticMerkleRootMultisigIsmFactory": "0xa9E0E18E78b098c2DE36c42E4DDEA13ce214c592", "staticMessageIdMultisigIsmFactory": "0xEa5Be2AD66BB1BA321B7aCf0A079fBE304B09Ca0", @@ -953,7 +1027,7 @@ "testTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb", "timelockController": "0x0000000000000000000000000000000000000000", "transactionOverrides": { - "maxFeePerGas": 800000000000, + "maxFeePerGas": 550000000000, "maxPriorityFeePerGas": 50000000000 }, "validatorAnnounce": "0x454E1a1E1CA8B51506090f1b5399083658eA4Fc5" @@ -980,6 +1054,7 @@ "domainRoutingIsmFactory": "0xe4057c5B0c43Dc18E36b08C39B419F190D29Ac2d", "fallbackRoutingHook": "0x01aE937A7B05d187bBCBE80F44F41879D3D335a4", "gasCurrencyCoinGeckoId": "ethereum", + "gnosisSafeTransactionServiceUrl": "https://safe-transaction-zkevm.safe.global/", "index": { "from": 6577743 }, @@ -1015,6 +1090,56 @@ "timelockController": "0x0000000000000000000000000000000000000000", "validatorAnnounce": "0x2fa5F5C96419C222cDbCeC797D696e6cE428A7A9" }, + "redstone": { + "blockExplorers": [ + { + "apiUrl": "https://explorer.redstone.xyz/api", + "family": "blockscout", + "name": "Redstone Explorer", + "url": "https://explorer.redstone.xyz" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 2, + "reorgPeriod": 0 + }, + "chainId": 690, + "displayName": "Redstone", + "domainId": 690, + "domainRoutingIsmFactory": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "fallbackRoutingHook": "0xA1ac41d8A663fd317cc3BD94C7de92dC4BA4a882", + "gasCurrencyCoinGeckoId": "ethereum", + "index": { + "from": 1797579 + }, + "interchainGasPaymaster": "0x2Fa570E83009eaEef3a1cbd496a9a30F05266634", + "interchainSecurityModule": "0xF4689C7fA4920C91a6EEEd59630C9C8da7a77D40", + "mailbox": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", + "merkleTreeHook": "0x8F1E22d309baa69D398a03cc88E9b46037e988AA", + "name": "redstone", + "nativeToken": { + "decimals": 18, + "name": "Ether", + "symbol": "ETH" + }, + "pausableHook": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", + "protocol": "ethereum", + "protocolFee": "0x26f32245fCF5Ad53159E875d5Cae62aEcf19c2d4", + "proxyAdmin": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", + "rpcUrls": [ + { + "http": "https://rpc.redstonechain.com" + } + ], + "staticAggregationHookFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", + "staticAggregationIsmFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", + "staticMerkleRootMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", + "staticMessageIdMultisigIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", + "storageGasOracle": "0x6Fae4D9935E2fcb11fC79a64e917fb2BF14DaFaa", + "testRecipient": "0x1Ab68dC4f7b6cfcd00218D4b761b7F3b5a724555", + "validatorAnnounce": "0x12582c7B0f43c6A667CBaA7fA8b112F7fb1E69F0" + }, "scroll": { "aggregationHook": "0x9Bc0FAf446E128a618A88a2F28960Fb2Ca169faE", "blockExplorers": [ @@ -1036,6 +1161,7 @@ "domainRoutingIsmFactory": "0xe03dad16074BC5EEA9A9311257BF02Eb0B6AAA2b", "fallbackRoutingHook": "0xDa7cECb05C4aeB02c1aFDE277d4306a2da7Bd762", "gasCurrencyCoinGeckoId": "ethereum", + "gnosisSafeTransactionServiceUrl": "https://transaction.safe.scroll.xyz", "index": { "chunk": 999, "from": 271840 @@ -1073,6 +1199,7 @@ "validatorAnnounce": "0xd83A4F747fE80Ed98839e05079B1B7Fe037b1638" }, "viction": { + "aggregationHook": "0x5c7890FAf9c99dC55926F00d624D7Bc6D7ac6834", "blockExplorers": [ { "apiUrl": "https://www.vicscan.xyz/api", @@ -1089,7 +1216,9 @@ "chainId": 88, "displayName": "Viction", "domainId": 88, + "domainRoutingIsm": "0x477145b11E1a71fEb658d96A0E27F19495121504", "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", + "fallbackRoutingHook": "0x5d69BC38eF3eDb491c0b7186BEc4eC45c4013f93", "gasCurrencyCoinGeckoId": "tomochain", "index": { "chunk": 999, @@ -1098,7 +1227,7 @@ "interchainAccountIsm": "0xD1E267d2d7876e97E217BfE61c34AB50FEF52807", "interchainAccountRouter": "0x1956848601549de5aa0c887892061fA5aB4f6fC4", "interchainGasPaymaster": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", - "interchainSecurityModule": "0xBD70Ea9D599a0FC8158B026797177773C3445730", + "interchainSecurityModule": "0xf8F3AF5F6B8f319364c339c0b8cA5975481901eD", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0x149db7afD694722747035d5AEC7007ccb6F8f112", "name": "viction", @@ -1107,6 +1236,8 @@ "name": "Viction", "symbol": "VIC" }, + "pausableHook": "0xDab56C5A1EffFdd23f6BD1243E457B1575984Bc6", + "pausableIsm": "0x92cdbF0Ccdf8E93467FA858fb986fa650A02f2A8", "protocol": "ethereum", "protocolFee": "0xd83A4F747fE80Ed98839e05079B1B7Fe037b1638", "proxyAdmin": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", @@ -1119,6 +1250,7 @@ } ], "staticAggregationHookFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", + "staticAggregationIsm": "0x60586f0b79426f8F406C807a59c7b6478e8bBa0C", "staticAggregationIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", "staticMerkleRootMultisigIsmFactory": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", "staticMessageIdMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", @@ -1127,6 +1259,62 @@ "testTokenRecipient": "0xe042D1fbDf59828dd16b9649Ede7abFc856F7a6c", "timelockController": "0x0000000000000000000000000000000000000000", "validatorAnnounce": "0x2fa5F5C96419C222cDbCeC797D696e6cE428A7A9" + }, + "zetachain": { + "blockExplorers": [ + { + "apiUrl": "https://explorer.zetachain.com", + "family": "other", + "name": "ZetaScan", + "url": "https://explorer.zetachain.com" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 6, + "reorgPeriod": 0 + }, + "chainId": 7000, + "displayName": "ZetaChain", + "domainId": 7000, + "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", + "fallbackRoutingHook": "0x8F1E22d309baa69D398a03cc88E9b46037e988AA", + "gasCurrencyCoinGeckoId": "zetachain", + "index": { + "from": 3068132 + }, + "interchainGasPaymaster": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", + "interchainSecurityModule": "0x8dfE6790DbB2Ecc1bEdb0eECfc1Ff467Ae5d8C89", + "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "merkleTreeHook": "0xE2ee936bEa8e42671c400aC96dE198E06F2bA2A6", + "name": "zetachain", + "nativeToken": { + "decimals": 18, + "name": "ZetaChain", + "symbol": "ZETA" + }, + "pausableHook": "0xA1ac41d8A663fd317cc3BD94C7de92dC4BA4a882", + "protocol": "ethereum", + "protocolFee": "0xea820f9BCFD5E16a0dd42071EB61A29874Ad81A4", + "proxyAdmin": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "rpcUrls": [ + { + "http": "https://zetachain-evm.blockpi.network/v1/rpc/public" + }, + { + "http": "https://zetachain-athens-evm.blockpi.network/v1/rpc/public" + }, + { + "http": "https://zetachain-mainnet-archive.allthatnode.com:8545" + } + ], + "staticAggregationHookFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", + "staticAggregationIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", + "staticMerkleRootMultisigIsmFactory": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", + "staticMessageIdMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", + "storageGasOracle": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", + "testRecipient": "0x12582c7B0f43c6A667CBaA7fA8b112F7fb1E69F0", + "validatorAnnounce": "0x48083C69f5a42c6B69ABbAd48AE195BD36770ee2" } }, "defaultRpcConsensusType": "fallback" diff --git a/rust/hyperlane-base/Cargo.toml b/rust/hyperlane-base/Cargo.toml index 97d84b622..9f401b399 100644 --- a/rust/hyperlane-base/Cargo.toml +++ b/rust/hyperlane-base/Cargo.toml @@ -15,6 +15,7 @@ axum.workspace = true bs58.workspace = true color-eyre = { workspace = true, optional = true } config.workspace = true +console-subscriber.workspace = true convert_case.workspace = true derive_builder.workspace = true derive-new.workspace = true diff --git a/rust/hyperlane-base/src/agent.rs b/rust/hyperlane-base/src/agent.rs index 5f6b504e9..153526d58 100644 --- a/rust/hyperlane-base/src/agent.rs +++ b/rust/hyperlane-base/src/agent.rs @@ -44,6 +44,7 @@ pub trait BaseAgent: Send + Sync + Debug { metrics: Arc, agent_metrics: AgentMetrics, chain_metrics: ChainMetrics, + tokio_console_server: console_subscriber::Server, ) -> Result where Self: Sized; @@ -75,10 +76,17 @@ pub async fn agent_main() -> Result<()> { let core_settings: &Settings = settings.as_ref(); let metrics = settings.as_ref().metrics(A::AGENT_NAME)?; - core_settings.tracing.start_tracing(&metrics)?; + let tokio_server = core_settings.tracing.start_tracing(&metrics)?; 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?; + let agent = A::from_settings( + settings, + metrics.clone(), + agent_metrics, + chain_metrics, + tokio_server, + ) + .await?; // This await will only end if a panic happens. We won't crash, but instead gracefully shut down agent.run().await; 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 index e217d4bb4..3efd04a8d 100644 --- a/rust/hyperlane-base/src/contract_sync/cursors/sequence_aware/backward.rs +++ b/rust/hyperlane-base/src/contract_sync/cursors/sequence_aware/backward.rs @@ -9,12 +9,11 @@ use hyperlane_core::{ HyperlaneSequenceAwareIndexerStoreReader, IndexMode, Indexed, LogMeta, SequenceIndexed, }; use itertools::Itertools; -use tracing::{debug, warn}; +use tracing::{debug, instrument, 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. @@ -34,6 +33,11 @@ pub(crate) struct BackwardSequenceAwareSyncCursor { } impl BackwardSequenceAwareSyncCursor { + #[instrument( + skip(db), + fields(chunk_size, next_sequence, start_block, index_mode), + ret + )] pub fn new( chunk_size: u32, db: Arc>, @@ -61,6 +65,7 @@ impl BackwardSequenceAwareSyncCursor { /// 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. + #[instrument(ret)] pub async fn get_next_range(&mut self) -> Result>> { // Skip any already indexed logs. self.skip_indexed().await?; @@ -129,6 +134,11 @@ impl BackwardSequenceAwareSyncCursor { // If the sequence hasn't been indexed, break out of the loop. break; } + // We've noticed that this loop can run for a long time because the `await` + // points never yield. + // So, to avoid starving other futures in this task, yield to the runtime + // on each iteration + tokio::task::yield_now().await; } Ok(()) @@ -299,6 +309,17 @@ impl BackwardSequenceAwareSyncCursor { } } +impl Debug for BackwardSequenceAwareSyncCursor { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BackwardSequenceAwareSyncCursor") + .field("chunk_size", &self.chunk_size) + .field("current_indexing_snapshot", &self.current_indexing_snapshot) + .field("last_indexed_snapshot", &self.last_indexed_snapshot) + .field("index_mode", &self.index_mode) + .finish() + } +} + #[async_trait] impl ContractSyncCursor for BackwardSequenceAwareSyncCursor @@ -329,6 +350,7 @@ impl ContractSyncCursor /// ## logs /// The logs to ingest. If any logs are duplicated or their sequence is higher than the current indexing snapshot, /// they are filtered out. + #[instrument(err, ret, skip(logs), fields(range=?range, logs=?logs.iter().map(|(log, _)| log.sequence).collect::>()))] async fn update( &mut self, logs: Vec<(Indexed, LogMeta)>, 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 index aef515b2b..374b4b797 100644 --- a/rust/hyperlane-base/src/contract_sync/cursors/sequence_aware/forward.rs +++ b/rust/hyperlane-base/src/contract_sync/cursors/sequence_aware/forward.rs @@ -13,12 +13,11 @@ use hyperlane_core::{ SequenceIndexed, }; use itertools::Itertools; -use tracing::{debug, warn}; +use tracing::{debug, instrument, 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. @@ -43,6 +42,11 @@ pub(crate) struct ForwardSequenceAwareSyncCursor { } impl ForwardSequenceAwareSyncCursor { + #[instrument( + skip(db, latest_sequence_querier), + fields(chunk_size, next_sequence, start_block, index_mode), + ret + )] pub fn new( chunk_size: u32, latest_sequence_querier: Arc>, @@ -76,6 +80,7 @@ impl ForwardSequenceAwareSyncCursor { /// 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. + #[instrument(ret)] pub async fn get_next_range(&mut self) -> Result>> { // Skip any already indexed logs. self.skip_indexed().await?; @@ -386,6 +391,18 @@ impl ForwardSequenceAwareSyncCursor { } } +impl Debug for ForwardSequenceAwareSyncCursor { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ForwardSequenceAwareSyncCursor") + .field("chunk_size", &self.chunk_size) + .field("current_indexing_snapshot", &self.current_indexing_snapshot) + .field("last_indexed_snapshot", &self.last_indexed_snapshot) + .field("target_snapshot", &self.target_snapshot) + .field("index_mode", &self.index_mode) + .finish() + } +} + #[async_trait] impl ContractSyncCursor for ForwardSequenceAwareSyncCursor @@ -420,6 +437,7 @@ impl ContractSyncCursor /// - 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. + #[instrument(err, ret, skip(logs), fields(range=?range, logs=?logs.iter().map(|(log, _)| log.sequence).collect::>()))] async fn update( &mut self, logs: Vec<(Indexed, LogMeta)>, diff --git a/rust/hyperlane-base/src/contract_sync/mod.rs b/rust/hyperlane-base/src/contract_sync/mod.rs index b97e3e5f4..85bf36c1c 100644 --- a/rust/hyperlane-base/src/contract_sync/mod.rs +++ b/rust/hyperlane-base/src/contract_sync/mod.rs @@ -82,7 +82,7 @@ where // from the loop (the sleep duration) #[allow(clippy::never_loop)] CursorAction::Query(range) => loop { - debug!(?range, "Looking for for events in index range"); + debug!(?range, "Looking for events in index range"); let logs = match self.indexer.fetch_logs(range.clone()).await { Ok(logs) => logs, diff --git a/rust/hyperlane-base/src/db/rocks/hyperlane_db.rs b/rust/hyperlane-base/src/db/rocks/hyperlane_db.rs index da61a26ce..3d164ce26 100644 --- a/rust/hyperlane-base/src/db/rocks/hyperlane_db.rs +++ b/rust/hyperlane-base/src/db/rocks/hyperlane_db.rs @@ -23,6 +23,7 @@ const MESSAGE_DISPATCHED_BLOCK_NUMBER: &str = "message_dispatched_block_number_" const MESSAGE: &str = "message_"; const NONCE_PROCESSED: &str = "nonce_processed_"; const GAS_PAYMENT_BY_SEQUENCE: &str = "gas_payment_by_sequence_"; +const HIGHEST_SEEN_MESSAGE_NONCE: &str = "highest_seen_message_nonce_"; const GAS_PAYMENT_FOR_MESSAGE_ID: &str = "gas_payment_sequence_for_message_id_v2_"; const GAS_PAYMENT_META_PROCESSED: &str = "gas_payment_meta_processed_v3_"; const GAS_EXPENDITURE_FOR_MESSAGE_ID: &str = "gas_expenditure_for_message_id_v2_"; @@ -34,7 +35,8 @@ const MERKLE_TREE_INSERTION_BLOCK_NUMBER_BY_LEAF_INDEX: &str = "merkle_tree_insertion_block_number_by_leaf_index_"; const LATEST_INDEXED_GAS_PAYMENT_BLOCK: &str = "latest_indexed_gas_payment_block"; -type DbResult = std::result::Result; +/// Rocks DB result type +pub type DbResult = std::result::Result; /// DB handle for storing data tied to a specific Mailbox. #[derive(Debug, Clone)] @@ -94,6 +96,8 @@ impl HyperlaneRocksDB { self.store_message_by_id(&id, message)?; // - `nonce` --> `id` self.store_message_id_by_nonce(&message.nonce, &id)?; + // Update the max seen nonce to allow forward-backward iteration in the processor + self.try_update_max_seen_message_nonce(message.nonce)?; // - `nonce` --> `dispatched block number` self.store_dispatched_block_number_by_nonce(&message.nonce, &dispatched_block_number)?; Ok(true) @@ -108,6 +112,22 @@ impl HyperlaneRocksDB { } } + /// Update the nonce of the highest processed message we're aware of + pub fn try_update_max_seen_message_nonce(&self, nonce: u32) -> DbResult<()> { + let current_max = self + .retrieve_highest_seen_message_nonce()? + .unwrap_or_default(); + if nonce >= current_max { + self.store_highest_seen_message_nonce_number(&Default::default(), &nonce)?; + } + Ok(()) + } + + /// Retrieve the nonce of the highest processed message we're aware of + pub fn retrieve_highest_seen_message_nonce(&self) -> DbResult> { + self.retrieve_highest_seen_message_nonce_number(&Default::default()) + } + /// If the provided gas payment, identified by its metadata, has not been /// processed, processes the gas payment and records it as processed. /// Returns whether the gas payment was processed for the first time. @@ -416,6 +436,39 @@ impl HyperlaneWatermarkedLogStore for HyperlaneRocksDB { } } +/// Database interface required for processing messages +pub trait ProcessMessage: Send + Sync { + /// Retrieve the nonce of the highest processed message we're aware of + fn retrieve_highest_seen_message_nonce(&self) -> DbResult>; + + /// Retrieve a message by its nonce + fn retrieve_message_by_nonce(&self, nonce: u32) -> DbResult>; + + /// Retrieve whether a message has been processed + fn retrieve_processed_by_nonce(&self, nonce: u32) -> DbResult>; + + /// Get the origin domain of the database + fn domain(&self) -> &HyperlaneDomain; +} + +impl ProcessMessage for HyperlaneRocksDB { + fn retrieve_highest_seen_message_nonce(&self) -> DbResult> { + self.retrieve_highest_seen_message_nonce() + } + + fn retrieve_message_by_nonce(&self, nonce: u32) -> DbResult> { + self.retrieve_message_by_nonce(nonce) + } + + fn retrieve_processed_by_nonce(&self, nonce: u32) -> DbResult> { + self.retrieve_processed_by_nonce(&nonce) + } + + fn domain(&self) -> &HyperlaneDomain { + self.domain() + } +} + /// Generate a call to ChainSetup for the given builder macro_rules! make_store_and_retrieve { ($vis:vis, $name_suffix:ident, $key_prefix: ident, $key_ty:ty, $val_ty:ty$(,)?) => { @@ -479,3 +532,6 @@ make_store_and_retrieve!( u32, u64 ); +// There's no unit struct Encode/Decode impl, so just use `bool`, have visibility be private (by omitting the first argument), and wrap +// with a function that always uses the `Default::default()` key +make_store_and_retrieve!(, highest_seen_message_nonce_number, HIGHEST_SEEN_MESSAGE_NONCE, bool, u32); diff --git a/rust/hyperlane-base/src/settings/mod.rs b/rust/hyperlane-base/src/settings/mod.rs index e80944432..aa7bee534 100644 --- a/rust/hyperlane-base/src/settings/mod.rs +++ b/rust/hyperlane-base/src/settings/mod.rs @@ -1,6 +1,6 @@ //! Common settings and configuration for Hyperlane agents //! -//! The correct settings shape is defined in the TypeScript SDK metadata. While the the exact shape +//! The correct settings shape is defined in the TypeScript SDK metadata. While the exact shape //! and validations it defines are not applied here, we should mirror them. //! ANY CHANGES HERE NEED TO BE REFLECTED IN THE TYPESCRIPT SDK. //! diff --git a/rust/hyperlane-base/src/settings/parser/mod.rs b/rust/hyperlane-base/src/settings/parser/mod.rs index 30430e04e..3c3a3aa65 100644 --- a/rust/hyperlane-base/src/settings/parser/mod.rs +++ b/rust/hyperlane-base/src/settings/parser/mod.rs @@ -1,6 +1,6 @@ //! This module is responsible for parsing the agent's settings. //! -//! The correct settings shape is defined in the TypeScript SDK metadata. While the the exact shape +//! The correct settings shape is defined in the TypeScript SDK metadata. While the exact shape //! and validations it defines are not applied here, we should mirror them. //! ANY CHANGES HERE NEED TO BE REFLECTED IN THE TYPESCRIPT SDK. diff --git a/rust/hyperlane-base/src/settings/trace/mod.rs b/rust/hyperlane-base/src/settings/trace/mod.rs index b0640f36e..00d9cb4c5 100644 --- a/rust/hyperlane-base/src/settings/trace/mod.rs +++ b/rust/hyperlane-base/src/settings/trace/mod.rs @@ -60,7 +60,7 @@ pub struct TracingConfig { impl TracingConfig { /// Attempt to instantiate and register a tracing subscriber setup from /// settings. - pub fn start_tracing(&self, metrics: &CoreMetrics) -> Result<()> { + pub fn start_tracing(&self, metrics: &CoreMetrics) -> Result { let mut target_layer = Targets::new().with_default(self.level); if self.level < Level::DependencyTrace { @@ -70,6 +70,7 @@ impl TracingConfig { .with_target("rusoto_core", Level::Info) .with_target("rustls", Level::Info) .with_target("reqwest", Level::Info) + .with_target("runtime", Level::Debug) .with_target("h2", Level::Info) .with_target("tower", Level::Info) .with_target("tendermint", Level::Info) @@ -85,13 +86,15 @@ impl TracingConfig { let fmt_layer: LogOutputLayer<_> = self.fmt.into(); let err_layer = tracing_error::ErrorLayer::default(); + let (tokio_layer, tokio_server) = console_subscriber::ConsoleLayer::new(); let subscriber = tracing_subscriber::Registry::default() + .with(tokio_layer) .with(target_layer) .with(TimeSpanLifetime::new(metrics)) .with(fmt_layer) .with(err_layer); subscriber.try_init()?; - Ok(()) + Ok(tokio_server) } } diff --git a/solidity/.solhint.json b/solidity/.solhint.json index a4cc08a3b..177b098d2 100644 --- a/solidity/.solhint.json +++ b/solidity/.solhint.json @@ -11,7 +11,6 @@ "func-name-mixedcase": "off", "reason-string": ["warn",{"maxLength":64}], "prettier/prettier": "error", - "custom-errors": "off", "gas-custom-errors": "off" }, "plugins": ["prettier"] diff --git a/solidity/.solhintignore b/solidity/.solhintignore index 9a4af1ecb..a1c868d03 100644 --- a/solidity/.solhintignore +++ b/solidity/.solhintignore @@ -1,2 +1,3 @@ contracts/mock contracts/test +contracts/interfaces/avs/vendored diff --git a/solidity/CHANGELOG.md b/solidity/CHANGELOG.md index e1735ad8e..f8cee2164 100644 --- a/solidity/CHANGELOG.md +++ b/solidity/CHANGELOG.md @@ -1,5 +1,17 @@ # @hyperlane-xyz/core +## 3.13.0 + +### Minor Changes + +- babe816f8: Support xERC20 and xERC20 Lockbox in SDK and CLI +- b440d98be: Added support for registering/deregistering from the Hyperlane AVS + +### Patch Changes + +- Updated dependencies [0cf692e73] + - @hyperlane-xyz/utils@3.13.0 + ## 3.12.0 ### Patch Changes diff --git a/solidity/README.md b/solidity/README.md index d800b4a1c..041d45fa6 100644 --- a/solidity/README.md +++ b/solidity/README.md @@ -28,7 +28,7 @@ yarn test ### Fixtures -Some forge tests may generate fixtures in the [fixtures](./fixtures/) directory. This allows [SDK](../typescript/sdk) tests to leverage forge fuzzing. These are git ignored and should not be committed. +Some forge tests may generate fixtures. This allows the [SDK](https://github.com/hyperlane-xyz/hyperlane-monorepo/tree/main/typescript/sdk) tests to leverage forge fuzzing. These are git ignored and should not be committed. ## License diff --git a/solidity/contracts/avs/ECDSAStakeRegistry.sol b/solidity/contracts/avs/ECDSAStakeRegistry.sol index 0a4e32a01..6148c3c4e 100644 --- a/solidity/contracts/avs/ECDSAStakeRegistry.sol +++ b/solidity/contracts/avs/ECDSAStakeRegistry.sol @@ -44,13 +44,14 @@ contract ECDSAStakeRegistry is __ECDSAStakeRegistry_init(_serviceManager, _thresholdWeight, _quorum); } - /// @notice Registers a new operator using a provided signature + /// @notice Registers a new operator using a provided signature and signing key /// @param _operatorSignature Contains the operator's signature, salt, and expiry + /// @param _signingKey The signing key to add to the operator's history function registerOperatorWithSignature( - address _operator, - ISignatureUtils.SignatureWithSaltAndExpiry memory _operatorSignature + ISignatureUtils.SignatureWithSaltAndExpiry memory _operatorSignature, + address _signingKey ) external { - _registerOperatorWithSig(_operator, _operatorSignature); + _registerOperatorWithSig(msg.sender, _operatorSignature, _signingKey); } /// @notice Deregisters an existing operator @@ -58,6 +59,18 @@ contract ECDSAStakeRegistry is _deregisterOperator(msg.sender); } + /** + * @notice Updates the signing key for an operator + * @dev Only callable by the operator themselves + * @param _newSigningKey The new signing key to set for the operator + */ + function updateOperatorSigningKey(address _newSigningKey) external { + if (!_operatorRegistered[msg.sender]) { + revert OperatorNotRegistered(); + } + _updateOperatorSigningKey(msg.sender, _newSigningKey); + } + /** * @notice Updates the StakeRegistry's view of one or more operators' stakes adding a new entry in their history of stake checkpoints, * @dev Queries stakes from the Eigenlayer core DelegationManager contract @@ -107,18 +120,18 @@ contract ECDSAStakeRegistry is /// @notice Verifies if the provided signature data is valid for the given data hash. /// @param _dataHash The hash of the data that was signed. - /// @param _signatureData Encoded signature data consisting of an array of signers, an array of signatures, and a reference block number. + /// @param _signatureData Encoded signature data consisting of an array of operators, an array of signatures, and a reference block number. /// @return The function selector that indicates the signature is valid according to ERC1271 standard. function isValidSignature( bytes32 _dataHash, bytes memory _signatureData ) external view returns (bytes4) { ( - address[] memory signers, + address[] memory operators, bytes[] memory signatures, uint32 referenceBlock ) = abi.decode(_signatureData, (address[], bytes[], uint32)); - _checkSignatures(_dataHash, signers, signatures, referenceBlock); + _checkSignatures(_dataHash, operators, signatures, referenceBlock); return IERC1271Upgradeable.isValidSignature.selector; } @@ -128,6 +141,37 @@ contract ECDSAStakeRegistry is return _quorum; } + /** + * @notice Retrieves the latest signing key for a given operator. + * @param _operator The address of the operator. + * @return The latest signing key of the operator. + */ + function getLastestOperatorSigningKey( + address _operator + ) external view returns (address) { + return address(uint160(_operatorSigningKeyHistory[_operator].latest())); + } + + /** + * @notice Retrieves the latest signing key for a given operator at a specific block number. + * @param _operator The address of the operator. + * @param _blockNumber The block number to get the operator's signing key. + * @return The signing key of the operator at the given block. + */ + function getOperatorSigningKeyAtBlock( + address _operator, + uint256 _blockNumber + ) external view returns (address) { + return + address( + uint160( + _operatorSigningKeyHistory[_operator].getAtBlock( + _blockNumber + ) + ) + ); + } + /// @notice Retrieves the last recorded weight for a given operator. /// @param _operator The address of the operator. /// @return uint256 - The latest weight of the operator. @@ -313,9 +357,11 @@ contract ECDSAStakeRegistry is /// @dev registers an operator through a provided signature /// @param _operatorSignature Contains the operator's signature, salt, and expiry + /// @param _signingKey The signing key to add to the operator's history function _registerOperatorWithSig( address _operator, - ISignatureUtils.SignatureWithSaltAndExpiry memory _operatorSignature + ISignatureUtils.SignatureWithSaltAndExpiry memory _operatorSignature, + address _signingKey ) internal virtual { if (_operatorRegistered[_operator]) { revert OperatorAlreadyRegistered(); @@ -324,6 +370,7 @@ contract ECDSAStakeRegistry is _operatorRegistered[_operator] = true; int256 delta = _updateOperatorWeight(_operator); _updateTotalWeight(delta); + _updateOperatorSigningKey(_operator, _signingKey); IServiceManager(_serviceManager).registerOperatorToAVS( _operator, _operatorSignature @@ -331,6 +378,28 @@ contract ECDSAStakeRegistry is emit OperatorRegistered(_operator, _serviceManager); } + /// @dev Internal function to update an operator's signing key + /// @param _operator The address of the operator to update the signing key for + /// @param _newSigningKey The new signing key to set for the operator + function _updateOperatorSigningKey( + address _operator, + address _newSigningKey + ) internal { + address oldSigningKey = address( + uint160(_operatorSigningKeyHistory[_operator].latest()) + ); + if (_newSigningKey == oldSigningKey) { + return; + } + _operatorSigningKeyHistory[_operator].push(uint160(_newSigningKey)); + emit SigningKeyUpdate( + _operator, + block.number, + _newSigningKey, + oldSigningKey + ); + } + /// @notice Updates the weight of an operator and returns the previous and current weights. /// @param _operator The address of the operator to update the weight of. function _updateOperatorWeight( @@ -401,30 +470,33 @@ contract ECDSAStakeRegistry is /** * @notice Common logic to verify a batch of ECDSA signatures against a hash, using either last stake weight or at a specific block. * @param _dataHash The hash of the data the signers endorsed. - * @param _signers A collection of addresses that endorsed the data hash. + * @param _operators A collection of addresses that endorsed the data hash. * @param _signatures A collection of signatures matching the signers. * @param _referenceBlock The block number for evaluating stake weight; use max uint32 for latest weight. */ function _checkSignatures( bytes32 _dataHash, - address[] memory _signers, + address[] memory _operators, bytes[] memory _signatures, uint32 _referenceBlock ) internal view { - uint256 signersLength = _signers.length; - address lastSigner; + uint256 signersLength = _operators.length; + address currentOperator; + address lastOperator; + address signer; uint256 signedWeight; _validateSignaturesLength(signersLength, _signatures.length); for (uint256 i; i < signersLength; i++) { - address currentSigner = _signers[i]; + currentOperator = _operators[i]; + signer = _getOperatorSigningKey(currentOperator, _referenceBlock); - _validateSortedSigners(lastSigner, currentSigner); - _validateSignature(currentSigner, _dataHash, _signatures[i]); + _validateSortedSigners(lastOperator, currentOperator); + _validateSignature(signer, _dataHash, _signatures[i]); - lastSigner = currentSigner; + lastOperator = currentOperator; uint256 operatorWeight = _getOperatorWeight( - currentSigner, + currentOperator, _referenceBlock ); signedWeight += operatorWeight; @@ -474,6 +546,27 @@ contract ECDSAStakeRegistry is } } + /// @notice Retrieves the operator weight for a signer, either at the last checkpoint or a specified block. + /// @param _operator The operator to query their signing key history for + /// @param _referenceBlock The block number to query the operator's weight at, or the maximum uint32 value for the last checkpoint. + /// @return The weight of the operator. + function _getOperatorSigningKey( + address _operator, + uint32 _referenceBlock + ) internal view returns (address) { + if (_referenceBlock >= block.number) { + revert InvalidReferenceBlock(); + } + return + address( + uint160( + _operatorSigningKeyHistory[_operator].getAtBlock( + _referenceBlock + ) + ) + ); + } + /// @notice Retrieves the operator weight for a signer, either at the last checkpoint or a specified block. /// @param _signer The address of the signer whose weight is returned. /// @param _referenceBlock The block number to query the operator's weight at, or the maximum uint32 value for the last checkpoint. @@ -482,11 +575,10 @@ contract ECDSAStakeRegistry is address _signer, uint32 _referenceBlock ) internal view returns (uint256) { - if (_referenceBlock == type(uint32).max) { - return _operatorWeightHistory[_signer].latest(); - } else { - return _operatorWeightHistory[_signer].getAtBlock(_referenceBlock); + if (_referenceBlock >= block.number) { + revert InvalidReferenceBlock(); } + return _operatorWeightHistory[_signer].getAtBlock(_referenceBlock); } /// @notice Retrieve the total stake weight at a specific block or the latest if not specified. @@ -496,11 +588,10 @@ contract ECDSAStakeRegistry is function _getTotalWeight( uint32 _referenceBlock ) internal view returns (uint256) { - if (_referenceBlock == type(uint32).max) { - return _totalWeightHistory.latest(); - } else { - return _totalWeightHistory.getAtBlock(_referenceBlock); + if (_referenceBlock >= block.number) { + revert InvalidReferenceBlock(); } + return _totalWeightHistory.getAtBlock(_referenceBlock); } /// @notice Retrieves the threshold stake for a given reference block. @@ -510,11 +601,10 @@ contract ECDSAStakeRegistry is function _getThresholdStake( uint32 _referenceBlock ) internal view returns (uint256) { - if (_referenceBlock == type(uint32).max) { - return _thresholdWeightHistory.latest(); - } else { - return _thresholdWeightHistory.getAtBlock(_referenceBlock); + if (_referenceBlock >= block.number) { + revert InvalidReferenceBlock(); } + return _thresholdWeightHistory.getAtBlock(_referenceBlock); } /// @notice Validates that the cumulative stake of signed messages meets or exceeds the required threshold. diff --git a/solidity/contracts/avs/ECDSAStakeRegistryStorage.sol b/solidity/contracts/avs/ECDSAStakeRegistryStorage.sol index 9e59fd8f6..28a584ca5 100644 --- a/solidity/contracts/avs/ECDSAStakeRegistryStorage.sol +++ b/solidity/contracts/avs/ECDSAStakeRegistryStorage.sol @@ -30,6 +30,10 @@ abstract contract ECDSAStakeRegistryStorage is /// @notice Defines the duration after which the stake's weight expires. uint256 internal _stakeExpiry; + /// @notice Maps an operator to their signing key history using checkpoints + mapping(address => CheckpointsUpgradeable.History) + internal _operatorSigningKeyHistory; + /// @notice Tracks the total stake history over time using checkpoints CheckpointsUpgradeable.History internal _totalWeightHistory; @@ -51,5 +55,5 @@ abstract contract ECDSAStakeRegistryStorage is // slither-disable-next-line shadowing-state /// @dev Reserves storage slots for future upgrades // solhint-disable-next-line - uint256[42] private __gap; + uint256[40] private __gap; } diff --git a/solidity/contracts/interfaces/avs/vendored/IECDSAStakeRegistryEventsAndErrors.sol b/solidity/contracts/interfaces/avs/vendored/IECDSAStakeRegistryEventsAndErrors.sol index 021d34db4..07f6323da 100644 --- a/solidity/contracts/interfaces/avs/vendored/IECDSAStakeRegistryEventsAndErrors.sol +++ b/solidity/contracts/interfaces/avs/vendored/IECDSAStakeRegistryEventsAndErrors.sol @@ -12,8 +12,6 @@ struct Quorum { StrategyParams[] strategies; // An array of strategy parameters to define the quorum } -/// part of mock interfaces for vendoring necessary Eigenlayer contracts for the hyperlane AVS -/// @author Layr Labs, Inc. interface IECDSAStakeRegistryEventsAndErrors { /// @notice Emitted when the system registers an operator /// @param _operator The address of the registered operator @@ -61,7 +59,19 @@ interface IECDSAStakeRegistryEventsAndErrors { /// @notice Emits when setting a new threshold weight. event ThresholdWeightUpdated(uint256 _thresholdWeight); + /// @notice Emitted when an operator's signing key is updated + /// @param operator The address of the operator whose signing key was updated + /// @param updateBlock The block number at which the signing key was updated + /// @param newSigningKey The operator's signing key after the update + /// @param oldSigningKey The operator's signing key before the update + event SigningKeyUpdate( + address indexed operator, + uint256 indexed updateBlock, + address indexed newSigningKey, + address oldSigningKey + ); /// @notice Indicates when the lengths of the signers array and signatures array do not match. + error LengthMismatch(); /// @notice Indicates encountering an invalid length for the signers or signatures array. @@ -76,6 +86,9 @@ interface IECDSAStakeRegistryEventsAndErrors { /// @notice Thrown when missing operators in an update error MustUpdateAllOperators(); + /// @notice Reference blocks must be for blocks that have already been confirmed + error InvalidReferenceBlock(); + /// @notice Indicates operator weights were out of sync and the signed weight exceed the total error InvalidSignedWeight(); diff --git a/solidity/contracts/test/ERC20Test.sol b/solidity/contracts/test/ERC20Test.sol index 87f38420e..8d4580c24 100644 --- a/solidity/contracts/test/ERC20Test.sol +++ b/solidity/contracts/test/ERC20Test.sol @@ -65,4 +65,16 @@ contract XERC20Test is ERC20Test, IXERC20 { function burn(address account, uint256 amount) public override { _burn(account, amount); } + + function setLimits( + address _bridge, + uint256 _mintingLimit, + uint256 _burningLimit + ) external { + require(false); + } + + function owner() external returns (address) { + return address(0x0); + } } diff --git a/solidity/contracts/token/extensions/HypFiatTokenCollateral.sol b/solidity/contracts/token/extensions/HypFiatToken.sol similarity index 94% rename from solidity/contracts/token/extensions/HypFiatTokenCollateral.sol rename to solidity/contracts/token/extensions/HypFiatToken.sol index ab043e641..c31351abc 100644 --- a/solidity/contracts/token/extensions/HypFiatTokenCollateral.sol +++ b/solidity/contracts/token/extensions/HypFiatToken.sol @@ -5,7 +5,7 @@ import {IFiatToken} from "../interfaces/IFiatToken.sol"; import {HypERC20Collateral} from "../HypERC20Collateral.sol"; // see https://github.com/circlefin/stablecoin-evm/blob/master/doc/tokendesign.md#issuing-and-destroying-tokens -contract HypFiatTokenCollateral is HypERC20Collateral { +contract HypFiatToken is HypERC20Collateral { constructor( address _fiatToken, address _mailbox diff --git a/solidity/contracts/token/extensions/HypXERC20Collateral.sol b/solidity/contracts/token/extensions/HypXERC20.sol similarity index 93% rename from solidity/contracts/token/extensions/HypXERC20Collateral.sol rename to solidity/contracts/token/extensions/HypXERC20.sol index f58b526ba..9f50b4537 100644 --- a/solidity/contracts/token/extensions/HypXERC20Collateral.sol +++ b/solidity/contracts/token/extensions/HypXERC20.sol @@ -4,7 +4,7 @@ pragma solidity >=0.8.0; import {IXERC20} from "../interfaces/IXERC20.sol"; import {HypERC20Collateral} from "../HypERC20Collateral.sol"; -contract HypXERC20Collateral is HypERC20Collateral { +contract HypXERC20 is HypERC20Collateral { constructor( address _xerc20, address _mailbox diff --git a/solidity/contracts/token/extensions/HypXERC20Lockbox.sol b/solidity/contracts/token/extensions/HypXERC20Lockbox.sol new file mode 100644 index 000000000..f4a860917 --- /dev/null +++ b/solidity/contracts/token/extensions/HypXERC20Lockbox.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.8.0; + +import {IXERC20Lockbox} from "../interfaces/IXERC20Lockbox.sol"; +import {IXERC20, IERC20} from "../interfaces/IXERC20.sol"; +import {HypERC20Collateral} from "../HypERC20Collateral.sol"; + +contract HypXERC20Lockbox is HypERC20Collateral { + uint256 constant MAX_INT = 2 ** 256 - 1; + + IXERC20Lockbox public immutable lockbox; + IXERC20 public immutable xERC20; + + constructor( + address _lockbox, + address _mailbox + ) HypERC20Collateral(address(IXERC20Lockbox(_lockbox).ERC20()), _mailbox) { + lockbox = IXERC20Lockbox(_lockbox); + xERC20 = lockbox.XERC20(); + + // grant infinite approvals to lockbox + require( + IERC20(wrappedToken).approve(_lockbox, MAX_INT), + "erc20 lockbox approve failed" + ); + require( + xERC20.approve(_lockbox, MAX_INT), + "xerc20 lockbox approve failed" + ); + } + + function _transferFromSender( + uint256 _amount + ) internal override returns (bytes memory) { + // transfer erc20 from sender + super._transferFromSender(_amount); + // convert erc20 to xERC20 + lockbox.deposit(_amount); + // burn xERC20 + xERC20.burn(address(this), _amount); + return bytes(""); + } + + function _transferTo( + address _recipient, + uint256 _amount, + bytes calldata /*metadata*/ + ) internal override { + // mint xERC20 + xERC20.mint(address(this), _amount); + // convert xERC20 to erc20 + lockbox.withdrawTo(_recipient, _amount); + } +} diff --git a/solidity/contracts/token/interfaces/IXERC20.sol b/solidity/contracts/token/interfaces/IXERC20.sol index 3f63c477a..2c9bad49a 100644 --- a/solidity/contracts/token/interfaces/IXERC20.sol +++ b/solidity/contracts/token/interfaces/IXERC20.sol @@ -21,4 +21,19 @@ interface IXERC20 is IERC20 { * @param _amount The amount of tokens being burned */ function burn(address _user, uint256 _amount) external; + + /** + * @notice Updates the limits of any bridge + * @dev Can only be called by the owner + * @param _mintingLimit The updated minting limit we are setting to the bridge + * @param _burningLimit The updated burning limit we are setting to the bridge + * @param _bridge The address of the bridge we are setting the limits too + */ + function setLimits( + address _bridge, + uint256 _mintingLimit, + uint256 _burningLimit + ) external; + + function owner() external returns (address); } diff --git a/solidity/contracts/token/interfaces/IXERC20Lockbox.sol b/solidity/contracts/token/interfaces/IXERC20Lockbox.sol new file mode 100644 index 000000000..ba01f7bc4 --- /dev/null +++ b/solidity/contracts/token/interfaces/IXERC20Lockbox.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.4 <0.9.0; + +// adapted from https://github.com/defi-wonderland/xERC20 + +import {IXERC20} from "./IXERC20.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +interface IXERC20Lockbox { + /** + * @notice The XERC20 token of this contract + */ + function XERC20() external returns (IXERC20); + + /** + * @notice The ERC20 token of this contract + */ + function ERC20() external returns (IERC20); + + /** + * @notice Deposit ERC20 tokens into the lockbox + * + * @param _amount The amount of tokens to deposit + */ + + function deposit(uint256 _amount) external; + + /** + * @notice Deposit ERC20 tokens into the lockbox, and send the XERC20 to a user + * + * @param _user The user to send the XERC20 to + * @param _amount The amount of tokens to deposit + */ + + function depositTo(address _user, uint256 _amount) external; + + /** + * @notice Deposit the native asset into the lockbox, and send the XERC20 to a user + * + * @param _user The user to send the XERC20 to + */ + + function depositNativeTo(address _user) external payable; + + /** + * @notice Withdraw ERC20 tokens from the lockbox + * + * @param _amount The amount of tokens to withdraw + */ + + function withdraw(uint256 _amount) external; + + /** + * @notice Withdraw ERC20 tokens from the lockbox + * + * @param _user The user to withdraw to + * @param _amount The amount of tokens to withdraw + */ + + function withdrawTo(address _user, uint256 _amount) external; +} diff --git a/solidity/package.json b/solidity/package.json index 3115dab14..bdbe1f659 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.12.2", + "version": "3.13.0", "dependencies": { "@eth-optimism/contracts": "^0.6.0", - "@hyperlane-xyz/utils": "3.12.2", + "@hyperlane-xyz/utils": "3.13.0", "@layerzerolabs/lz-evm-oapp-v2": "2.0.2", "@openzeppelin/contracts": "^4.9.3", "@openzeppelin/contracts-upgradeable": "^v4.9.3", diff --git a/solidity/script/avs/DeployAVS.s.sol b/solidity/script/avs/DeployAVS.s.sol index f2fce2d1d..b38762be6 100644 --- a/solidity/script/avs/DeployAVS.s.sol +++ b/solidity/script/avs/DeployAVS.s.sol @@ -12,6 +12,7 @@ import {ProxyAdmin} from "../../contracts/upgrade/ProxyAdmin.sol"; import {TransparentUpgradeableProxy} from "../../contracts/upgrade/TransparentUpgradeableProxy.sol"; import {ECDSAStakeRegistry} from "../../contracts/avs/ECDSAStakeRegistry.sol"; import {Quorum, StrategyParams} from "../../contracts/interfaces/avs/vendored/IECDSAStakeRegistryEventsAndErrors.sol"; +import {ECDSAServiceManagerBase} from "../../contracts/avs/ECDSAServiceManagerBase.sol"; import {HyperlaneServiceManager} from "../../contracts/avs/HyperlaneServiceManager.sol"; import {TestPaymentCoordinator} from "../../contracts/test/avs/TestPaymentCoordinator.sol"; @@ -42,6 +43,11 @@ contract DeployAVS is Script { ); string memory json = vm.readFile(path); + proxyAdmin = ProxyAdmin( + json.readAddress( + string(abi.encodePacked(".", targetEnv, ".proxyAdmin")) + ) + ); avsDirectory = IAVSDirectory( json.readAddress( string(abi.encodePacked(".", targetEnv, ".avsDirectory")) @@ -88,15 +94,14 @@ contract DeployAVS is Script { } } - function run(string memory network) external { + function run(string memory network, string memory metadataUri) external { deployerPrivateKey = vm.envUint("DEPLOYER_PRIVATE_KEY"); + address deployerAddress = vm.addr(deployerPrivateKey); _loadEigenlayerAddresses(network); vm.startBroadcast(deployerPrivateKey); - proxyAdmin = new ProxyAdmin(); - ECDSAStakeRegistry stakeRegistryImpl = new ECDSAStakeRegistry( delegationManager ); @@ -118,7 +123,7 @@ contract DeployAVS is Script { address(proxyAdmin), abi.encodeWithSelector( HyperlaneServiceManager.initialize.selector, - msg.sender + address(deployerAddress) ) ); @@ -131,7 +136,24 @@ contract DeployAVS is Script { quorum ) ); + + HyperlaneServiceManager hsm = HyperlaneServiceManager( + address(hsmProxy) + ); + require(success, "Failed to initialize ECDSAStakeRegistry"); + require( + ECDSAStakeRegistry(address(stakeRegistryProxy)).owner() == + address(deployerAddress), + "Owner of ECDSAStakeRegistry is not the deployer" + ); + require( + HyperlaneServiceManager(address(hsmProxy)).owner() == + address(deployerAddress), + "Owner of HyperlaneServiceManager is not the deployer" + ); + + hsm.updateAVSMetadataURI(metadataUri); console.log( "ECDSAStakeRegistry Implementation: ", diff --git a/solidity/script/avs/eigenlayer_addresses.json b/solidity/script/avs/eigenlayer_addresses.json index d8890a77b..60e2fceea 100644 --- a/solidity/script/avs/eigenlayer_addresses.json +++ b/solidity/script/avs/eigenlayer_addresses.json @@ -1,5 +1,6 @@ { "ethereum": { + "proxyAdmin": "0x75EE15Ee1B4A75Fa3e2fDF5DF3253c25599cc659", "delegationManager": "0x39053D51B77DC0d36036Fc1fCc8Cb819df8Ef37A", "avsDirectory": "0x135DDa560e946695d6f155dACaFC6f1F25C1F5AF", "paymentCoordinator": "", @@ -19,6 +20,7 @@ ] }, "holesky": { + "proxyAdmin": "0x33dB966328Ea213b0f76eF96CA368AB37779F065", "delegationManager": "0xA44151489861Fe9e3055d95adC98FbD462B948e7", "avsDirectory": "0x055733000064333CaDDbC92763c58BF0192fFeBf", "paymentCoordinator": "", diff --git a/solidity/test/avs/HyperlaneServiceManager.t.sol b/solidity/test/avs/HyperlaneServiceManager.t.sol index 4ea9ce8f1..80d49f974 100644 --- a/solidity/test/avs/HyperlaneServiceManager.t.sol +++ b/solidity/test/avs/HyperlaneServiceManager.t.sol @@ -29,6 +29,7 @@ contract HyperlaneServiceManagerTest is EigenlayerBase { // Operator info uint256 operatorPrivateKey = 0xdeadbeef; address operator; + address avsSigningKey = address(0xc0ffee); bytes32 emptySalt; uint256 maxExpiry = type(uint256).max; @@ -97,9 +98,11 @@ contract HyperlaneServiceManagerTest is EigenlayerBase { emptySalt, maxExpiry ); + + vm.prank(operator); _ecdsaStakeRegistry.registerOperatorWithSignature( - operator, - operatorSignature + operatorSignature, + avsSigningKey ); // assert @@ -122,12 +125,13 @@ contract HyperlaneServiceManagerTest is EigenlayerBase { maxExpiry ); + vm.prank(operator); vm.expectRevert( "EIP1271SignatureUtils.checkSignature_EIP1271: signature not from signer" ); _ecdsaStakeRegistry.registerOperatorWithSignature( - operator, - operatorSignature + operatorSignature, + avsSigningKey ); // assert @@ -409,9 +413,10 @@ contract HyperlaneServiceManagerTest is EigenlayerBase { maxExpiry ); + vm.prank(operator); _ecdsaStakeRegistry.registerOperatorWithSignature( - operator, - operatorSignature + operatorSignature, + avsSigningKey ); } diff --git a/solidity/test/token/HypERC20.t.sol b/solidity/test/token/HypERC20.t.sol index 5be0bd5ed..82c5359b7 100644 --- a/solidity/test/token/HypERC20.t.sol +++ b/solidity/test/token/HypERC20.t.sol @@ -28,8 +28,8 @@ import {HypERC20} from "../../contracts/token/HypERC20.sol"; import {HypERC20Collateral} from "../../contracts/token/HypERC20Collateral.sol"; import {IXERC20} from "../../contracts/token/interfaces/IXERC20.sol"; import {IFiatToken} from "../../contracts/token/interfaces/IFiatToken.sol"; -import {HypXERC20Collateral} from "../../contracts/token/extensions/HypXERC20Collateral.sol"; -import {HypFiatTokenCollateral} from "../../contracts/token/extensions/HypFiatTokenCollateral.sol"; +import {HypXERC20} from "../../contracts/token/extensions/HypXERC20.sol"; +import {HypFiatToken} from "../../contracts/token/extensions/HypFiatToken.sol"; import {HypNative} from "../../contracts/token/HypNative.sol"; import {TokenRouter} from "../../contracts/token/libs/TokenRouter.sol"; import {TokenMessage} from "../../contracts/token/libs/TokenMessage.sol"; @@ -394,20 +394,20 @@ contract HypERC20CollateralTest is HypTokenTest { } } -contract HypXERC20CollateralTest is HypTokenTest { +contract HypXERC20Test is HypTokenTest { using TypeCasts for address; - HypXERC20Collateral internal xerc20Collateral; + HypXERC20 internal xerc20Collateral; function setUp() public override { super.setUp(); primaryToken = new XERC20Test(NAME, SYMBOL, TOTAL_SUPPLY, DECIMALS); - localToken = new HypXERC20Collateral( + localToken = new HypXERC20( address(primaryToken), address(localMailbox) ); - xerc20Collateral = HypXERC20Collateral(address(localToken)); + xerc20Collateral = HypXERC20(address(localToken)); xerc20Collateral.enrollRemoteRouter( DESTINATION, @@ -442,22 +442,22 @@ contract HypXERC20CollateralTest is HypTokenTest { } } -contract HypFiatTokenCollateralTest is HypTokenTest { +contract HypFiatTokenTest is HypTokenTest { using TypeCasts for address; - HypFiatTokenCollateral internal fiatTokenCollateral; + HypFiatToken internal fiatToken; function setUp() public override { super.setUp(); primaryToken = new FiatTokenTest(NAME, SYMBOL, TOTAL_SUPPLY, DECIMALS); - localToken = new HypFiatTokenCollateral( + localToken = new HypFiatToken( address(primaryToken), address(localMailbox) ); - fiatTokenCollateral = HypFiatTokenCollateral(address(localToken)); + fiatToken = HypFiatToken(address(localToken)); - fiatTokenCollateral.enrollRemoteRouter( + fiatToken.enrollRemoteRouter( DESTINATION, address(remoteToken).addressToBytes32() ); diff --git a/typescript/ccip-server/CHANGELOG.md b/typescript/ccip-server/CHANGELOG.md index bcee12745..40e0e5390 100644 --- a/typescript/ccip-server/CHANGELOG.md +++ b/typescript/ccip-server/CHANGELOG.md @@ -1,5 +1,7 @@ # @hyperlane-xyz/ccip-server +## 3.13.0 + ## 3.12.0 ## 3.11.1 diff --git a/typescript/ccip-server/package.json b/typescript/ccip-server/package.json index f7a9829c8..cc07a17b3 100644 --- a/typescript/ccip-server/package.json +++ b/typescript/ccip-server/package.json @@ -1,6 +1,6 @@ { "name": "@hyperlane-xyz/ccip-server", - "version": "3.12.2", + "version": "3.13.0", "description": "CCIP server", "typings": "dist/index.d.ts", "typedocMain": "src/index.ts", diff --git a/typescript/cli/CHANGELOG.md b/typescript/cli/CHANGELOG.md index 8f42fe354..c62b58046 100644 --- a/typescript/cli/CHANGELOG.md +++ b/typescript/cli/CHANGELOG.md @@ -1,5 +1,23 @@ # @hyperlane-xyz/cli +## 3.13.0 + +### Minor Changes + +- b22a0f453: Add hyperlane validator address command to retrieve validator address from AWS +- 39ea7cdef: Implement multi collateral warp routes +- babe816f8: Support xERC20 and xERC20 Lockbox in SDK and CLI +- b440d98be: Added support for registering/deregistering from the Hyperlane AVS + +### Patch Changes + +- b6b26e2bb: fix: minor change was breaking in registry export +- Updated dependencies [39ea7cdef] +- Updated dependencies [babe816f8] +- Updated dependencies [0cf692e73] + - @hyperlane-xyz/sdk@3.13.0 + - @hyperlane-xyz/utils@3.13.0 + ## 3.12.0 ### Minor Changes diff --git a/typescript/cli/README.md b/typescript/cli/README.md index 20fe12cda..8493414cd 100644 --- a/typescript/cli/README.md +++ b/typescript/cli/README.md @@ -12,7 +12,7 @@ To read more about interchain applications, how the protocol works, and how to i ## Setup -Node 16 or newer is required. +Node 18 or newer is required. **Option 1: Global install:** diff --git a/typescript/cli/cli.ts b/typescript/cli/cli.ts index 0ced1ef0f..a8b9127f3 100644 --- a/typescript/cli/cli.ts +++ b/typescript/cli/cli.ts @@ -5,6 +5,7 @@ import yargs from 'yargs'; import type { LogFormat, LogLevel } from '@hyperlane-xyz/utils'; import './env.js'; +import { avsCommand } from './src/commands/avs.js'; import { chainsCommand } from './src/commands/chains.js'; import { configCommand } from './src/commands/config.js'; import { deployCommand } from './src/commands/deploy.js'; @@ -20,6 +21,7 @@ import { } from './src/commands/options.js'; import { sendCommand } from './src/commands/send.js'; import { statusCommand } from './src/commands/status.js'; +import { validatorCommand } from './src/commands/validator.js'; import { contextMiddleware } from './src/context/context.js'; import { configureLogger, errorRed } from './src/logger.js'; import { checkVersion } from './src/utils/version-check.js'; @@ -48,6 +50,7 @@ try { }, contextMiddleware, ]) + .command(avsCommand) .command(chainsCommand) .command(configCommand) .command(deployCommand) @@ -55,6 +58,7 @@ try { .command(ismCommand) .command(sendCommand) .command(statusCommand) + .command(validatorCommand) .version(VERSION) .demandCommand() .strict() diff --git a/typescript/cli/package.json b/typescript/cli/package.json index 4d5ddf2db..645d3a48b 100644 --- a/typescript/cli/package.json +++ b/typescript/cli/package.json @@ -1,12 +1,15 @@ { "name": "@hyperlane-xyz/cli", - "version": "3.12.2", + "version": "3.13.0", "description": "A command-line utility for common Hyperlane operations", "dependencies": { - "@hyperlane-xyz/registry": "^1.0.7", - "@hyperlane-xyz/sdk": "3.12.2", - "@hyperlane-xyz/utils": "3.12.2", + "@aws-sdk/client-kms": "^3.577.0", + "@aws-sdk/client-s3": "^3.577.0", + "@hyperlane-xyz/registry": "1.3.0", + "@hyperlane-xyz/sdk": "3.13.0", + "@hyperlane-xyz/utils": "3.13.0", "@inquirer/prompts": "^3.0.0", + "asn1.js": "^5.4.1", "bignumber.js": "^9.1.1", "chalk": "^5.3.0", "ethers": "^5.7.2", diff --git a/typescript/cli/src/avs/config.ts b/typescript/cli/src/avs/config.ts new file mode 100644 index 000000000..681ed9dee --- /dev/null +++ b/typescript/cli/src/avs/config.ts @@ -0,0 +1,19 @@ +import { ChainMap } from '@hyperlane-xyz/sdk'; +import { Address } from '@hyperlane-xyz/utils'; + +interface AVSContracts { + avsDirectory: Address; + proxyAdmin: Address; + ecdsaStakeRegistry: Address; + hyperlaneServiceManager: Address; +} + +// TODO: move to registry +export const avsAddresses: ChainMap = { + holesky: { + avsDirectory: '0x055733000064333CaDDbC92763c58BF0192fFeBf', + proxyAdmin: '0x33dB966328Ea213b0f76eF96CA368AB37779F065', + ecdsaStakeRegistry: '0xFfa913705484C9BAea32Ffe9945BeA099A1DFF72', + hyperlaneServiceManager: '0xc76E477437065093D353b7d56c81ff54D167B0Ab', + }, +}; diff --git a/typescript/cli/src/avs/stakeRegistry.ts b/typescript/cli/src/avs/stakeRegistry.ts new file mode 100644 index 000000000..9d23bffaa --- /dev/null +++ b/typescript/cli/src/avs/stakeRegistry.ts @@ -0,0 +1,164 @@ +import { password } from '@inquirer/prompts'; +import { BigNumberish, Wallet, utils } from 'ethers'; + +import { + ECDSAStakeRegistry__factory, + TestAVSDirectory__factory, +} from '@hyperlane-xyz/core'; +import { ChainName } from '@hyperlane-xyz/sdk'; +import { Address } from '@hyperlane-xyz/utils'; + +import { WriteCommandContext } from '../context/types.js'; +import { log, logBlue } from '../logger.js'; +import { readFileAtPath, resolvePath } from '../utils/files.js'; + +import { avsAddresses } from './config.js'; + +export type SignatureWithSaltAndExpiryStruct = { + signature: utils.BytesLike; + salt: utils.BytesLike; + expiry: BigNumberish; +}; + +export async function registerOperatorWithSignature({ + context, + chain, + operatorKeyPath, + avsSigningKey, +}: { + context: WriteCommandContext; + chain: ChainName; + operatorKeyPath: string; + avsSigningKey: Address; +}) { + const { multiProvider } = context; + + const operatorAsSigner = await readOperatorFromEncryptedJson(operatorKeyPath); + + const provider = multiProvider.getProvider(chain); + const connectedSigner = operatorAsSigner.connect(provider); + + const stakeRegistryAddress = avsAddresses[chain].ecdsaStakeRegistry; + + const ecdsaStakeRegistry = ECDSAStakeRegistry__factory.connect( + stakeRegistryAddress, + connectedSigner, + ); + + const domainId = multiProvider.getDomainId(chain); + const avsDirectoryAddress = avsAddresses[chain].avsDirectory; + const operatorSignature = await getOperatorSignature( + domainId, + avsAddresses[chain].hyperlaneServiceManager, + avsDirectoryAddress, + operatorAsSigner, + connectedSigner, + ); + + // check if the operator is already registered + const operatorStatus = await ecdsaStakeRegistry.operatorRegistered( + operatorAsSigner.address, + ); + if (operatorStatus) { + logBlue( + `Operator ${operatorAsSigner.address} already registered to Hyperlane AVS`, + ); + return; + } + + log( + `Registering operator ${operatorAsSigner.address} attesting ${avsSigningKey} with signature on ${chain}...`, + ); + await multiProvider.handleTx( + chain, + ecdsaStakeRegistry.registerOperatorWithSignature( + operatorSignature, + avsSigningKey, + ), + ); + logBlue(`Operator ${operatorAsSigner.address} registered to Hyperlane AVS`); +} + +export async function deregisterOperator({ + context, + chain, + operatorKeyPath, +}: { + context: WriteCommandContext; + chain: ChainName; + operatorKeyPath: string; +}) { + const { multiProvider } = context; + + const operatorAsSigner = await readOperatorFromEncryptedJson(operatorKeyPath); + + const provider = multiProvider.getProvider(chain); + const connectedSigner = operatorAsSigner.connect(provider); + + const stakeRegistryAddress = avsAddresses[chain].ecdsaStakeRegistry; + + const ecdsaStakeRegistry = ECDSAStakeRegistry__factory.connect( + stakeRegistryAddress, + connectedSigner, + ); + + log(`Deregistering operator ${operatorAsSigner.address} on ${chain}...`); + await multiProvider.handleTx(chain, ecdsaStakeRegistry.deregisterOperator()); + logBlue( + `Operator ${operatorAsSigner.address} deregistered from Hyperlane AVS`, + ); +} + +async function readOperatorFromEncryptedJson( + operatorKeyPath: string, +): Promise { + const encryptedJson = readFileAtPath(resolvePath(operatorKeyPath)); + + const keyFilePassword = await password({ + mask: '*', + message: 'Enter the password for the operator key file: ', + }); + + return await Wallet.fromEncryptedJson(encryptedJson, keyFilePassword); +} + +async function getOperatorSignature( + domain: number, + serviceManager: Address, + avsDirectory: Address, + operator: Wallet, + signer: Wallet, +): Promise { + const avsDirectoryContract = TestAVSDirectory__factory.connect( + avsDirectory, + signer, + ); + + // random salt is ok, because we register the operator right after + const salt = utils.hexZeroPad(utils.randomBytes(32), 32); + // give a expiry timestamp 1 hour from now + const expiry = utils.hexZeroPad( + utils.hexlify(Math.floor(Date.now() / 1000) + 60 * 60), + 32, + ); + + const signingHash = + await avsDirectoryContract.calculateOperatorAVSRegistrationDigestHash( + operator.address, + serviceManager, + salt, + expiry, + ); + + // Eigenlayer's AVSDirectory expects the signature over raw signed hash instead of EIP-191 compatible toEthSignedMessageHash + // see https://github.com/Layr-Labs/eigenlayer-contracts/blob/ef2ea4a7459884f381057aa9bbcd29c7148cfb63/src/contracts/libraries/EIP1271SignatureUtils.sol#L22 + const signature = operator + ._signingKey() + .signDigest(utils.arrayify(signingHash)); + + return { + signature: utils.joinSignature(signature), + salt, + expiry, + }; +} diff --git a/typescript/cli/src/commands/avs.ts b/typescript/cli/src/commands/avs.ts new file mode 100644 index 000000000..04a51b6b6 --- /dev/null +++ b/typescript/cli/src/commands/avs.ts @@ -0,0 +1,84 @@ +import { CommandModule, Options } from 'yargs'; + +import { ChainName } from '@hyperlane-xyz/sdk'; +import { Address } from '@hyperlane-xyz/utils'; + +import { + deregisterOperator, + registerOperatorWithSignature, +} from '../avs/stakeRegistry.js'; +import { CommandModuleWithWriteContext } from '../context/types.js'; +import { log } from '../logger.js'; + +/** + * Parent command + */ +export const avsCommand: CommandModule = { + command: 'avs', + describe: 'Interact with the Hyperlane AVS', + builder: (yargs) => + yargs + .command(registerCommand) + .command(deregisterCommand) + .version(false) + .demandCommand(), + handler: () => log('Command required'), +}; + +/** + * Registration command + */ +export const registrationOptions: { [k: string]: Options } = { + chain: { + type: 'string', + description: 'Chain to interact with the AVS on', + demandOption: true, + choices: ['holesky', 'ethereum'], + }, + operatorKeyPath: { + type: 'string', + description: 'Path to the operator key file', + demandOption: true, + }, + avsSigningKey: { + type: 'string', + description: 'Address of the AVS signing key', + demandOption: true, + }, +}; + +const registerCommand: CommandModuleWithWriteContext<{ + chain: ChainName; + operatorKeyPath: string; + avsSigningKey: Address; +}> = { + command: 'register', + describe: 'Register operator with the AVS', + builder: registrationOptions, + handler: async ({ context, chain, operatorKeyPath, avsSigningKey }) => { + await registerOperatorWithSignature({ + context, + chain, + operatorKeyPath, + avsSigningKey, + }); + process.exit(0); + }, +}; + +const deregisterCommand: CommandModuleWithWriteContext<{ + chain: ChainName; + operatorKeyPath: string; +}> = { + command: 'deregister', + describe: 'Deregister yourself with the AVS', + builder: registrationOptions, + handler: async ({ context, chain, operatorKeyPath }) => { + await deregisterOperator({ + context, + chain, + operatorKeyPath, + }); + process.exit(0); + }, +}; diff --git a/typescript/cli/src/commands/config.ts b/typescript/cli/src/commands/config.ts index b498db4b9..bf30e78ee 100644 --- a/typescript/cli/src/commands/config.ts +++ b/typescript/cli/src/commands/config.ts @@ -170,7 +170,7 @@ const validateWarpCommand: CommandModuleWithContext<{ path: string }> = { path: inputFileCommandOption, }, handler: async ({ path }) => { - readWarpRouteDeployConfig(path); + await readWarpRouteDeployConfig(path); logGreen('Config is valid'); process.exit(0); }, diff --git a/typescript/cli/src/commands/options.ts b/typescript/cli/src/commands/options.ts index aa2b7e036..3774b43cb 100644 --- a/typescript/cli/src/commands/options.ts +++ b/typescript/cli/src/commands/options.ts @@ -146,3 +146,35 @@ export const addressCommandOption = ( description, demandOption, }); + +/* Validator options */ +export const awsAccessKeyCommandOption: Options = { + type: 'string', + description: 'AWS access key of IAM user associated with validator', + default: ENV.AWS_ACCESS_KEY_ID, + defaultDescription: 'process.env.AWS_ACCESS_KEY_ID', +}; + +export const awsSecretKeyCommandOption: Options = { + type: 'string', + description: 'AWS secret access key of IAM user associated with validator', + default: ENV.AWS_SECRET_ACCESS_KEY, + defaultDescription: 'process.env.AWS_SECRET_ACCESS_KEY', +}; + +export const awsRegionCommandOption: Options = { + type: 'string', + describe: 'AWS region associated with validator', + default: ENV.AWS_REGION, + defaultDescription: 'process.env.AWS_REGION', +}; + +export const awsBucketCommandOption: Options = { + type: 'string', + describe: 'AWS S3 bucket containing validator signatures and announcement', +}; + +export const awsKeyIdCommandOption: Options = { + type: 'string', + describe: 'Key ID from AWS KMS', +}; diff --git a/typescript/cli/src/commands/validator.ts b/typescript/cli/src/commands/validator.ts new file mode 100644 index 000000000..973c0cd25 --- /dev/null +++ b/typescript/cli/src/commands/validator.ts @@ -0,0 +1,51 @@ +import { CommandModule } from 'yargs'; + +import { CommandModuleWithContext } from '../context/types.js'; +import { log } from '../logger.js'; +import { getValidatorAddress } from '../validator/address.js'; + +import { + awsAccessKeyCommandOption, + awsBucketCommandOption, + awsKeyIdCommandOption, + awsRegionCommandOption, + awsSecretKeyCommandOption, +} from './options.js'; + +// Parent command to help configure and set up Hyperlane validators +export const validatorCommand: CommandModule = { + command: 'validator', + describe: 'Configure and manage Hyperlane validators', + builder: (yargs) => yargs.command(addressCommand).demandCommand(), + handler: () => log('Command required'), +}; + +// If AWS access key needed for future validator commands, move to context +const addressCommand: CommandModuleWithContext<{ + accessKey: string; + secretKey: string; + region: string; + bucket: string; + keyId: string; +}> = { + command: 'address', + describe: 'Get the validator address from S3 bucket or KMS key ID', + builder: { + 'access-key': awsAccessKeyCommandOption, + 'secret-key': awsSecretKeyCommandOption, + region: awsRegionCommandOption, + bucket: awsBucketCommandOption, + 'key-id': awsKeyIdCommandOption, + }, + handler: async ({ context, accessKey, secretKey, region, bucket, keyId }) => { + await getValidatorAddress({ + context, + accessKey, + secretKey, + region, + bucket, + keyId, + }); + process.exit(0); + }, +}; diff --git a/typescript/cli/src/config/chain.ts b/typescript/cli/src/config/chain.ts index 232205dc0..c2655cab3 100644 --- a/typescript/cli/src/config/chain.ts +++ b/typescript/cli/src/config/chain.ts @@ -46,8 +46,8 @@ export async function createChainConfig({ await new ethers.providers.JsonRpcProvider().getNetwork(); return ethers.providers.JsonRpcProvider.defaultUrl(); }, - 'rpc url', 'Enter http or https', + 'rpc url', ); const provider = new ethers.providers.JsonRpcProvider(rpcUrl); @@ -58,8 +58,8 @@ export async function createChainConfig({ const client = clientName.split('/')[0]; return `${client}${port}`; }, - 'chain name', 'Enter (one word, lower case)', + 'chain name', ); const chainId = parseInt( @@ -68,8 +68,8 @@ export async function createChainConfig({ const network = await provider.getNetwork(); return network.chainId.toString(); }, - 'chain id', 'Enter a (number)', + 'chain id', ), 10, ); diff --git a/typescript/cli/src/config/warp.ts b/typescript/cli/src/config/warp.ts index 19f1a1fbd..d9ca9b4e5 100644 --- a/typescript/cli/src/config/warp.ts +++ b/typescript/cli/src/config/warp.ts @@ -1,30 +1,89 @@ -import { confirm, input } from '@inquirer/prompts'; -import { ethers } from 'ethers'; +import { input, select } from '@inquirer/prompts'; import { - ChainMetadata, + ChainMap, + MailboxClientConfig, TokenType, WarpCoreConfig, WarpCoreConfigSchema, WarpRouteDeployConfig, WarpRouteDeployConfigSchema, } from '@hyperlane-xyz/sdk'; -import { objFilter } from '@hyperlane-xyz/utils'; +import { assert, objMap, promiseObjAll } from '@hyperlane-xyz/utils'; import { CommandContext } from '../context/types.js'; import { errorRed, logBlue, logGreen } from '../logger.js'; import { + detectAndConfirmOrPrompt, runMultiChainSelectionStep, - runSingleChainSelectionStep, } from '../utils/chains.js'; import { readYamlOrJson, writeYamlOrJson } from '../utils/files.js'; -export function readWarpRouteDeployConfig( +const TYPE_DESCRIPTIONS: Record = { + [TokenType.synthetic]: 'A new ERC20 with remote transfer functionality', + [TokenType.collateral]: + 'Extends an existing ERC20 with remote transfer functionality', + [TokenType.native]: + 'Extends the native token with remote transfer functionality', + [TokenType.collateralVault]: + 'Extends an existing ERC4626 with remote transfer functionality', + [TokenType.collateralFiat]: + 'Extends an existing FiatToken with remote transfer functionality', + [TokenType.XERC20]: + 'Extends an existing xERC20 with Warp Route functionality', + [TokenType.XERC20Lockbox]: + 'Extends an existing xERC20 Lockbox with Warp Route functionality', + // TODO: describe + [TokenType.fastSynthetic]: '', + [TokenType.syntheticUri]: '', + [TokenType.fastCollateral]: '', + [TokenType.collateralUri]: '', + [TokenType.nativeScaled]: '', +}; + +const TYPE_CHOICES = Object.values(TokenType).map((type) => ({ + name: type, + value: type, + description: TYPE_DESCRIPTIONS[type], +})); + +async function fillDefaults( + context: CommandContext, + config: ChainMap>, +): Promise> { + return promiseObjAll( + objMap(config, async (chain, config): Promise => { + let mailbox = config.mailbox; + if (!mailbox) { + const addresses = await context.registry.getChainAddresses(chain); + assert(addresses, `No addresses found for chain ${chain}`); + mailbox = addresses.mailbox; + } + let owner = config.owner; + if (!owner) { + owner = + (await context.signer?.getAddress()) ?? + (await context.multiProvider.getSignerAddress(chain)); + } + return { + owner, + mailbox, + ...config, + }; + }), + ); +} + +export async function readWarpRouteDeployConfig( filePath: string, -): WarpRouteDeployConfig { - const config = readYamlOrJson(filePath); + context?: CommandContext, +): Promise { + let config = readYamlOrJson(filePath); if (!config) throw new Error(`No warp route deploy config found at ${filePath}`); + if (context) { + config = await fillDefaults(context, config as any); + } return WarpRouteDeployConfigSchema.parse(config); } @@ -40,75 +99,71 @@ export async function createWarpRouteDeployConfig({ outPath: string; }) { logBlue('Creating a new warp route deployment config'); - const baseChain = await runSingleChainSelectionStep( - context.chainMetadata, - 'Select base chain with the original token to warp', + + const owner = await detectAndConfirmOrPrompt( + async () => context.signer?.getAddress(), + 'Enter the desired', + 'owner address', ); - const isNative = await confirm({ - message: - 'Are you creating a route for the native token of the base chain (e.g. Ether on Ethereum)?', - }); - - const isNft = isNative - ? false - : await confirm({ message: 'Is this an NFT (i.e. ERC-721)?' }); - const isYieldBearing = - isNative || isNft - ? false - : await confirm({ - message: - 'Do you want this warp route to be yield-bearing (i.e. deposits into ERC-4626 vault)?', - }); - - const addressMessage = `Enter the ${ - isYieldBearing ? 'ERC-4626 vault' : 'collateral token' - } address`; - const baseAddress = isNative - ? ethers.constants.AddressZero - : await input({ message: addressMessage }); - - const metadataWithoutBase = objFilter( + const warpChains = await runMultiChainSelectionStep( context.chainMetadata, - (chain, _): _ is ChainMetadata => chain !== baseChain, - ); - const syntheticChains = await runMultiChainSelectionStep( - metadataWithoutBase, - 'Select chains to which the base token will be connected', + 'Select chains to connect', ); - // TODO add more prompts here to support customizing the token metadata - let result: WarpRouteDeployConfig; - if (isNative) { - result = { - [baseChain]: { - type: TokenType.native, - }, - }; - } else { - result = { - [baseChain]: { - type: isYieldBearing ? TokenType.collateralVault : TokenType.collateral, - token: baseAddress, - isNft, + const result: WarpRouteDeployConfig = {}; + for (const chain of warpChains) { + logBlue(`Configuring warp route for chain ${chain}`); + const type = await select({ + message: `Select ${chain}'s token type`, + choices: TYPE_CHOICES, + }); + + // TODO: restore NFT prompting + const isNft = + type === TokenType.syntheticUri || type === TokenType.collateralUri; + + const mailbox = await detectAndConfirmOrPrompt( + async () => { + const addresses = await context.registry.getChainAddresses(chain); + return addresses?.mailbox; }, - }; - } + `For ${chain}, enter the`, + 'mailbox address', + ); - syntheticChains.map((chain) => { - result[chain] = { - type: TokenType.synthetic, - }; - }); + switch (type) { + case TokenType.collateral: + case TokenType.XERC20: + case TokenType.XERC20Lockbox: + case TokenType.collateralFiat: + case TokenType.collateralUri: + case TokenType.fastCollateral: + case TokenType.collateralVault: + result[chain] = { + mailbox, + type, + owner, + isNft, + token: await input({ + message: `Enter the existing token address on chain ${chain}`, + }), + }; + break; + default: + result[chain] = { mailbox, type, owner, isNft }; + } + } - if (isValidWarpRouteDeployConfig(result)) { + try { + const parsed = WarpRouteDeployConfigSchema.parse(result); logGreen(`Warp Route config is valid, writing to file ${outPath}`); - writeYamlOrJson(outPath, result); - } else { + writeYamlOrJson(outPath, parsed); + } catch (e) { errorRed( `Warp route deployment config is invalid, please see https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/cli/examples/warp-route-deployment.yaml for an example`, ); - throw new Error('Invalid multisig config'); + throw e; } } diff --git a/typescript/cli/src/consts.ts b/typescript/cli/src/consts.ts index 9e05f2fcb..584ad97b9 100644 --- a/typescript/cli/src/consts.ts +++ b/typescript/cli/src/consts.ts @@ -1,3 +1,4 @@ export const MINIMUM_CORE_DEPLOY_GAS = (1e8).toString(); export const MINIMUM_WARP_DEPLOY_GAS = (1e7).toString(); export const MINIMUM_TEST_SEND_GAS = (3e5).toString(); +export const MINIMUM_AVS_GAS = (3e6).toString(); diff --git a/typescript/cli/src/context/context.ts b/typescript/cli/src/context/context.ts index b153854df..fba159a69 100644 --- a/typescript/cli/src/context/context.ts +++ b/typescript/cli/src/context/context.ts @@ -1,13 +1,17 @@ import { ethers } from 'ethers'; -import { IRegistry } from '@hyperlane-xyz/registry'; +import { + GithubRegistry, + IRegistry, + MergedRegistry, +} from '@hyperlane-xyz/registry'; +import { FileSystemRegistry } from '@hyperlane-xyz/registry/fs'; import { ChainName, MultiProvider } from '@hyperlane-xyz/sdk'; -import { isNullish } from '@hyperlane-xyz/utils'; +import { isHttpsUrl, isNullish, rootLogger } from '@hyperlane-xyz/utils'; import { isSignCommand } from '../commands/signCommands.js'; import { forkNetworkToMultiProvider, verifyAnvil } from '../deploy/dry-run.js'; import { logBlue } from '../logger.js'; -import { MergedRegistry } from '../registry/MergedRegistry.js'; import { runSingleChainSelectionStep } from '../utils/chains.js'; import { getImpersonatedSigner, getSigner } from '../utils/keys.js'; @@ -81,7 +85,7 @@ export async function getDryRunContext( }: ContextSettings, chain?: ChainName, ): Promise { - const registry = getRegistry(registryUri, registryOverrideUri, true); + const registry = getRegistry(registryUri, registryOverrideUri); const chainMetadata = await registry.getMetadata(); if (!chain) { @@ -127,14 +131,25 @@ export async function getDryRunContext( function getRegistry( primaryRegistryUri: string, overrideRegistryUri: string, - isDryRun?: boolean, ): IRegistry { - const registryUris = [primaryRegistryUri, overrideRegistryUri] - .map((r) => r.trim()) - .filter((r) => !!r); + const logger = rootLogger.child({ module: 'MergedRegistry' }); + const registries = [primaryRegistryUri, overrideRegistryUri] + .map((uri) => uri.trim()) + .filter((uri) => !!uri) + .map((uri, index) => { + const childLogger = logger.child({ uri, index }); + if (isHttpsUrl(uri)) { + return new GithubRegistry({ uri, logger: childLogger }); + } else { + return new FileSystemRegistry({ + uri, + logger: childLogger, + }); + } + }); return new MergedRegistry({ - registryUris, - isDryRun, + registries, + logger, }); } diff --git a/typescript/cli/src/deploy/core.ts b/typescript/cli/src/deploy/core.ts index f3da40e48..8f8bf2043 100644 --- a/typescript/cli/src/deploy/core.ts +++ b/typescript/cli/src/deploy/core.ts @@ -258,6 +258,7 @@ async function executeDeploy({ registry, ismFactoryContracts, artifacts, + context.isDryRun, ); logGreen('ISM factory contracts deployed'); @@ -297,7 +298,12 @@ async function executeDeploy({ }; } artifacts = objMerge(artifacts, isms); - artifacts = await updateChainAddresses(registry, coreContracts, artifacts); + artifacts = await updateChainAddresses( + registry, + coreContracts, + artifacts, + context.isDryRun, + ); logGreen('✅ Core contracts deployed'); log(JSON.stringify(artifacts, null, 2)); @@ -395,6 +401,7 @@ async function updateChainAddresses( registry: IRegistry, newContracts: HyperlaneContractsMap, otherAddresses: HyperlaneAddressesMap, + isDryRun?: boolean, ) { let newAddresses = serializeContractsMap(newContracts); // The HyperlaneCoreDeployer is returning a nested object with ISM addresses @@ -407,6 +414,9 @@ async function updateChainAddresses( ); }); const mergedAddresses = objMerge(otherAddresses, newAddresses); + + if (isDryRun) return mergedAddresses; + for (const chainName of Object.keys(newContracts)) { await registry.updateChain({ chainName, diff --git a/typescript/cli/src/deploy/utils.ts b/typescript/cli/src/deploy/utils.ts index a2f411d2e..f1082b301 100644 --- a/typescript/cli/src/deploy/utils.ts +++ b/typescript/cli/src/deploy/utils.ts @@ -18,36 +18,6 @@ import { assertSigner } from '../utils/keys.js'; import { completeDryRun } from './dry-run.js'; -export async function runPreflightChecks({ - context, - origin, - remotes, - minGas, - chainsToGasCheck, -}: { - context: WriteCommandContext; - origin: ChainName; - remotes: ChainName[]; - minGas: string; - chainsToGasCheck?: ChainName[]; -}) { - log('Running pre-flight checks...'); - - if (!origin || !remotes?.length) throw new Error('Invalid chain selection'); - logGreen('✅ Chain selections are valid'); - - if (remotes.includes(origin)) - throw new Error('Origin and remotes must be distinct'); - logGreen('✅ Origin and remote are distinct'); - - return runPreflightChecksForChains({ - context, - chains: [origin, ...remotes], - minGas, - chainsToGasCheck, - }); -} - export async function runPreflightChecksForChains({ context, chains, diff --git a/typescript/cli/src/deploy/warp.ts b/typescript/cli/src/deploy/warp.ts index a4a92c465..0968f0d99 100644 --- a/typescript/cli/src/deploy/warp.ts +++ b/typescript/cli/src/deploy/warp.ts @@ -1,38 +1,35 @@ -import { confirm, input } from '@inquirer/prompts'; +import { confirm } from '@inquirer/prompts'; import { - ChainMap, - ChainName, - ConnectionClientConfig, - EvmTokenAdapter, HypERC20Deployer, HypERC721Deployer, HyperlaneContractsMap, - MinimalTokenMetadata, - MultiProtocolProvider, - MultiProvider, - RouterConfig, TOKEN_TYPE_TO_STANDARD, - TokenConfig, TokenFactories, - TokenRouterConfig, TokenType, WarpCoreConfig, WarpRouteDeployConfig, getTokenConnectionId, - isCollateralConfig, - isNativeConfig, - isSyntheticConfig, + isTokenMetadata, } from '@hyperlane-xyz/sdk'; import { ProtocolType } from '@hyperlane-xyz/utils'; import { readWarpRouteDeployConfig } from '../config/warp.js'; import { MINIMUM_WARP_DEPLOY_GAS } from '../consts.js'; import { WriteCommandContext } from '../context/types.js'; -import { log, logBlue, logGray, logGreen } from '../logger.js'; +import { log, logBlue, logGray, logGreen, logTable } from '../logger.js'; import { isFile, runFileSelectionStep } from '../utils/files.js'; -import { completeDeploy, prepareDeploy, runPreflightChecks } from './utils.js'; +import { + completeDeploy, + prepareDeploy, + runPreflightChecksForChains, +} from './utils.js'; + +interface DeployParams { + context: WriteCommandContext; + configMap: WarpRouteDeployConfig; +} export async function runWarpRouteDeploy({ context, @@ -59,30 +56,28 @@ export async function runWarpRouteDeploy({ `Using warp route deployment config at ${warpRouteDeploymentConfigPath}`, ); } - const warpRouteConfig = readWarpRouteDeployConfig( + const warpRouteConfig = await readWarpRouteDeployConfig( warpRouteDeploymentConfigPath, - ); - - const configs = await runBuildConfigStep({ context, - warpRouteConfig, - }); + ); const deploymentParams = { context, - ...configs, + configMap: warpRouteConfig, }; logBlue('Warp route deployment plan'); await runDeployPlanStep(deploymentParams); - await runPreflightChecks({ - ...deploymentParams, + const chains = Object.keys(warpRouteConfig); + + await runPreflightChecksForChains({ + context, + chains, minGas: MINIMUM_WARP_DEPLOY_GAS, }); const userAddress = await signer.getAddress(); - const chains = [deploymentParams.origin, ...configs.remotes]; const initialBalances = await prepareDeploy(context, userAddress, chains); @@ -91,111 +86,13 @@ export async function runWarpRouteDeploy({ await completeDeploy(context, 'warp', initialBalances, userAddress, chains); } -async function runBuildConfigStep({ - context, - warpRouteConfig, -}: { - context: WriteCommandContext; - warpRouteConfig: WarpRouteDeployConfig; -}) { - const { registry, signer, multiProvider, skipConfirmation } = context; - log('Assembling token configs'); - const chainAddresses = await registry.getAddresses(); - const owner = await signer.getAddress(); - const requiredRouterFields: Array = ['mailbox']; - const remotes: string[] = []; - - /// @dev This will keep track of the base collateral metadata which can get overwritten if there are multiple collaterals. - /// These 'base' variables are used to derive synthetic fields - /// @todo Remove this artifact when multi-collateral is enabled - let baseChainName = ''; - let baseMetadata = {} as MinimalTokenMetadata; - // Define configs that coalesce together values from the config file - for (const [chain, config] of Object.entries(warpRouteConfig)) { - // the artifacts, and the SDK as a fallback - config.owner = owner; - config.mailbox = config.mailbox || chainAddresses[chain]?.mailbox; - config.interchainSecurityModule = - config.interchainSecurityModule || - chainAddresses[chain]?.interchainSecurityModule || - chainAddresses[chain]?.multisigIsm; - // config.ismFactory: chainAddresses[baseChainName].domainRoutingIsmFactory, // TODO fix when updating from routingIsm - - if (isCollateralConfig(config) || isNativeConfig(config)) { - // Store the base metadata - baseChainName = chain; - baseMetadata = await fetchBaseTokenMetadata(chain, config, multiProvider); - log( - `Using token metadata: Name: ${baseMetadata.name}, Symbol: ${baseMetadata.symbol}, Decimals: ${baseMetadata.decimals}`, - ); - if (isCollateralConfig(config)) { - config.name = baseMetadata.name; - config.symbol = baseMetadata.symbol; - config.decimals = baseMetadata.decimals; - } - } else if (isSyntheticConfig(config)) { - // Use the config, or baseMetadata - config.name = config.name || baseMetadata.name; - config.symbol = config.symbol || baseMetadata.symbol; - config.totalSupply = config.totalSupply || 0; - remotes.push(chain); - } - - let hasShownInfo = false; - // Request input for any address fields that are missing - for (const field of requiredRouterFields) { - if (config[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.', - ); - hasShownInfo = true; - } - const value = await input({ - message: `Enter ${field} for ${getTokenName(config)} token on ${chain}`, - }); - if (!value) throw new Error(`Field ${field} required`); - config[field] = value.trim(); - } - } - - log('Token configs ready'); - return { - configMap: warpRouteConfig, - origin: baseChainName, - metadata: baseMetadata, - remotes, - }; -} - -interface DeployParams { - context: WriteCommandContext; - configMap: WarpRouteDeployConfig; - metadata: MinimalTokenMetadata; - origin: ChainName; - remotes: ChainName[]; -} +async function runDeployPlanStep({ context, configMap }: DeployParams) { + const { skipConfirmation } = context; -async function runDeployPlanStep({ - context, - configMap, - origin, - remotes, -}: DeployParams) { - const { signer, skipConfirmation } = context; - const address = await signer.getAddress(); - const baseToken = configMap[origin]; - - const baseName = getTokenName(baseToken); logBlue('\nDeployment plan'); logGray('==============='); - log(`Collateral type will be ${baseToken.type}`); - log(`Transaction signer and owner of new contracts will be ${address}`); - log(`Deploying a warp route with a base of ${baseName} token on ${origin}`); - log(`Connecting it to new synthetic tokens on ${remotes.join(', ')}`); log(`Using token standard ${configMap.isNft ? 'ERC721' : 'ERC20'}`); + logTable(configMap); if (skipConfirmation) return; @@ -210,80 +107,67 @@ async function executeDeploy(params: DeployParams) { const { configMap, - context: { registry, multiProvider, isDryRun }, + context: { registry, multiProvider, isDryRun, dryRunChain }, } = params; const deployer = configMap.isNft ? new HypERC721Deployer(multiProvider) : new HypERC20Deployer(multiProvider); - const config = isDryRun - ? { [params.origin]: configMap[params.origin] } - : configMap; + const config: WarpRouteDeployConfig = + isDryRun && dryRunChain + ? { [dryRunChain]: configMap[dryRunChain] } + : configMap; - const deployedContracts = await deployer.deploy( - config as ChainMap, - ); /// @todo remove ChainMap once Hyperlane deployers are refactored + const deployedContracts = await deployer.deploy(config); logGreen('✅ Hyp token deployments complete'); - if (!isDryRun) log('Writing deployment artifacts'); - const warpCoreConfig = getWarpCoreConfig(params, deployedContracts); - await registry.addWarpRoute(warpCoreConfig); + const warpCoreConfig = await getWarpCoreConfig(params, deployedContracts); + if (!isDryRun) { + log('Writing deployment artifacts'); + await registry.addWarpRoute(warpCoreConfig); + } log(JSON.stringify(warpCoreConfig, null, 2)); logBlue('Deployment is complete!'); } -async function fetchBaseTokenMetadata( - chain: string, - config: TokenRouterConfig, - multiProvider: MultiProvider, -): Promise { - if (config.type === TokenType.native) { - // If it's a native token, use the chain's native token metadata - const chainNativeToken = multiProvider.getChainMetadata(chain).nativeToken; - if (chainNativeToken) return chainNativeToken; - else throw new Error(`No native token metadata for ${chain}`); - } else if ( - config.type === TokenType.collateralVault || - config.type === TokenType.collateral - ) { - // If it's a collateral type, use a TokenAdapter to query for its metadata - log(`Fetching token metadata for ${config.token} on ${chain}`); - const adapter = new EvmTokenAdapter( - chain, - MultiProtocolProvider.fromMultiProvider(multiProvider), - { token: config.token }, - ); - return adapter.getMetadata(); - } else { - throw new Error( - `Unsupported token: ${config.type}. Consider setting token metadata in your deployment config.`, - ); - } -} - -function getTokenName(token: TokenConfig) { - return token.type === TokenType.native ? 'native' : token.name; -} - -function getWarpCoreConfig( - { configMap, metadata }: DeployParams, +async function getWarpCoreConfig( + { configMap, context }: DeployParams, contracts: HyperlaneContractsMap, -): WarpCoreConfig { +): Promise { const warpCoreConfig: WarpCoreConfig = { tokens: [] }; + // TODO: replace with warp read + const tokenMetadata = await HypERC20Deployer.deriveTokenMetadata( + context.multiProvider, + configMap, + ); + // First pass, create token configs for (const [chainName, contract] of Object.entries(contracts)) { const config = configMap[chainName]; + const metadata = { + ...tokenMetadata, + ...config, + }; + + if (!isTokenMetadata(metadata)) { + throw new Error('Missing required token metadata'); + } + + const { decimals } = metadata; + if (!decimals) { + throw new Error('Missing decimals on token metadata'); + } + const collateralAddressOrDenom = config.type === TokenType.collateral ? config.token : undefined; warpCoreConfig.tokens.push({ chainName, standard: TOKEN_TYPE_TO_STANDARD[config.type], - name: metadata.name, - symbol: metadata.symbol, - decimals: metadata.decimals, + ...metadata, + decimals, addressOrDenom: contract[configMap[chainName].type as keyof TokenFactories].address, collateralAddressOrDenom, diff --git a/typescript/cli/src/registry/MergedRegistry.ts b/typescript/cli/src/registry/MergedRegistry.ts deleted file mode 100644 index fe6737cf8..000000000 --- a/typescript/cli/src/registry/MergedRegistry.ts +++ /dev/null @@ -1,156 +0,0 @@ -import { Logger } from 'pino'; - -import { - BaseRegistry, - ChainAddresses, - GithubRegistry, - IRegistry, - RegistryContent, - RegistryType, -} from '@hyperlane-xyz/registry'; -import { LocalRegistry } from '@hyperlane-xyz/registry/local'; -import { - ChainMap, - ChainMetadata, - ChainName, - WarpCoreConfig, -} from '@hyperlane-xyz/sdk'; -import { - isHttpsUrl, - objKeys, - objMerge, - rootLogger, -} from '@hyperlane-xyz/utils'; - -export interface MergedRegistryOptions { - registryUris: Array; - isDryRun?: boolean; - logger?: Logger; -} - -export class MergedRegistry extends BaseRegistry implements IRegistry { - public readonly type = RegistryType.Local; - public readonly registries: Array; - public readonly isDryRun: boolean; - - constructor({ registryUris, logger, isDryRun }: MergedRegistryOptions) { - logger ||= rootLogger.child({ module: 'MergedRegistry' }); - super({ uri: '__merged_registry__', logger }); - - if (!registryUris.length) - throw new Error('At least one registry URI is required'); - - this.registries = registryUris.map((uri, index) => { - if (isHttpsUrl(uri)) { - return new GithubRegistry({ uri, logger: logger!.child({ index }) }); - } else { - return new LocalRegistry({ uri, logger: logger!.child({ index }) }); - } - }); - - this.isDryRun = !!isDryRun; - } - - async listRegistryContent(): Promise { - const results = await this.multiRegistryRead((r) => - r.listRegistryContent(), - ); - return results.reduce((acc, content) => objMerge(acc, content), { - chains: {}, - deployments: {}, - }); - } - - async getChains(): Promise> { - return objKeys(await this.getMetadata); - } - - async getMetadata(): Promise> { - const results = await this.multiRegistryRead((r) => r.getMetadata()); - return results.reduce((acc, content) => objMerge(acc, content), {}); - } - - async getChainMetadata(chainName: ChainName): Promise { - return (await this.getMetadata())[chainName] || null; - } - - async getAddresses(): Promise> { - const results = await this.multiRegistryRead((r) => r.getAddresses()); - return results.reduce((acc, content) => objMerge(acc, content), {}); - } - - async getChainAddresses( - chainName: ChainName, - ): Promise { - return (await this.getAddresses())[chainName] || null; - } - - async addChain(chain: { - chainName: ChainName; - metadata?: ChainMetadata; - addresses?: ChainAddresses; - }): Promise { - return this.multiRegistryWrite( - async (registry) => await registry.addChain(chain), - `adding chain ${chain.chainName}`, - ); - } - - async updateChain(chain: { - chainName: ChainName; - metadata?: ChainMetadata; - addresses?: ChainAddresses; - }): Promise { - return this.multiRegistryWrite( - async (registry) => await registry.updateChain(chain), - `updating chain ${chain.chainName}`, - ); - } - - async removeChain(chain: ChainName): Promise { - return this.multiRegistryWrite( - async (registry) => await registry.removeChain(chain), - `removing chain ${chain}`, - ); - } - - async addWarpRoute(config: WarpCoreConfig): Promise { - return this.multiRegistryWrite( - async (registry) => await registry.addWarpRoute(config), - 'adding warp route', - ); - } - - protected multiRegistryRead( - readFn: (registry: IRegistry) => Promise | R, - ) { - return Promise.all(this.registries.map(readFn)); - } - - protected async multiRegistryWrite( - writeFn: (registry: IRegistry) => Promise, - logMsg: string, - ): Promise { - if (this.isDryRun) return; - for (const registry of this.registries) { - // TODO remove this when GithubRegistry supports write methods - if (registry.type === RegistryType.Github) { - this.logger.warn(`skipping ${logMsg} at ${registry.type} registry`); - continue; - } - try { - this.logger.info( - `${logMsg} at ${registry.type} registry at ${registry.uri}`, - ); - await writeFn(registry); - this.logger.info(`done ${logMsg} at ${registry.type} registry`); - } catch (error) { - // To prevent loss of artifacts, MergedRegistry write methods are failure tolerant - this.logger.error( - `failure ${logMsg} at ${registry.type} registry`, - error, - ); - } - } - } -} diff --git a/typescript/cli/src/send/message.ts b/typescript/cli/src/send/message.ts index e48e71237..102592bbc 100644 --- a/typescript/cli/src/send/message.ts +++ b/typescript/cli/src/send/message.ts @@ -5,7 +5,7 @@ import { addressToBytes32, timeout } from '@hyperlane-xyz/utils'; import { MINIMUM_TEST_SEND_GAS } from '../consts.js'; import { CommandContext, WriteCommandContext } from '../context/types.js'; -import { runPreflightChecks } from '../deploy/utils.js'; +import { runPreflightChecksForChains } from '../deploy/utils.js'; import { errorRed, log, logBlue, logGreen } from '../logger.js'; import { runSingleChainSelectionStep } from '../utils/chains.js'; @@ -42,12 +42,11 @@ export async function sendTestMessage({ ); } - await runPreflightChecks({ + await runPreflightChecksForChains({ context, - origin, - remotes: [destination], - minGas: MINIMUM_TEST_SEND_GAS, + chains: [origin, destination], chainsToGasCheck: [origin], + minGas: MINIMUM_TEST_SEND_GAS, }); await timeout( diff --git a/typescript/cli/src/send/transfer.ts b/typescript/cli/src/send/transfer.ts index dcd3aa6fd..23cd5ba52 100644 --- a/typescript/cli/src/send/transfer.ts +++ b/typescript/cli/src/send/transfer.ts @@ -13,7 +13,7 @@ import { timeout } from '@hyperlane-xyz/utils'; import { readWarpRouteConfig } from '../config/warp.js'; import { MINIMUM_TEST_SEND_GAS } from '../consts.js'; import { WriteCommandContext } from '../context/types.js'; -import { runPreflightChecks } from '../deploy/utils.js'; +import { runPreflightChecksForChains } from '../deploy/utils.js'; import { logBlue, logGreen, logRed } from '../logger.js'; import { runSingleChainSelectionStep } from '../utils/chains.js'; import { runTokenSelectionStep } from '../utils/tokens.js'; @@ -57,12 +57,11 @@ export async function sendTestTransfer({ ); } - await runPreflightChecks({ + await runPreflightChecksForChains({ context, - origin, - remotes: [destination], - minGas: MINIMUM_TEST_SEND_GAS, + chains: [origin, destination], chainsToGasCheck: [origin], + minGas: MINIMUM_TEST_SEND_GAS, }); await timeout( diff --git a/typescript/cli/src/tests/deployTestErc20.ts b/typescript/cli/src/tests/deployTestErc20.ts index 517668a59..be49a13da 100644 --- a/typescript/cli/src/tests/deployTestErc20.ts +++ b/typescript/cli/src/tests/deployTestErc20.ts @@ -2,7 +2,7 @@ import { Wallet, providers } from 'ethers'; import fs from 'fs'; import { ERC20Test__factory } from '@hyperlane-xyz/core'; -import { TokenType, WarpRouteDeployConfig } from '@hyperlane-xyz/sdk'; +import { TokenType } from '@hyperlane-xyz/sdk'; async function deployERC20() { const [rpcUrl, chain1, chain2, privateKey, outPath] = process.argv.slice(2); @@ -19,13 +19,14 @@ async function deployERC20() { await contract.deployed(); console.log('Test ERC20 contract deployed', contract.address); - const warpDeploymentConfig: WarpRouteDeployConfig = { + const warpDeploymentConfig = { [chain1]: { type: TokenType.collateral, token: contract.address, - isNft: false, }, - [chain2]: { type: TokenType.synthetic }, + [chain2]: { + type: TokenType.synthetic, + }, }; console.log('Writing deployment config to', outPath); diff --git a/typescript/cli/src/utils/chains.ts b/typescript/cli/src/utils/chains.ts index 470f0a20d..3c0e7477d 100644 --- a/typescript/cli/src/utils/chains.ts +++ b/typescript/cli/src/utils/chains.ts @@ -75,18 +75,20 @@ function handleNewChain(chainNames: string[]) { } export async function detectAndConfirmOrPrompt( - detect: () => Promise, - label: string, + detect: () => Promise, prompt: string, + label: string, ): Promise { let detectedValue: string | undefined; try { detectedValue = await detect(); - const confirmed = await confirm({ - message: `Detected ${label} as ${detectedValue}, is this correct?`, - }); - if (confirmed) { - return detectedValue; + if (detectedValue) { + const confirmed = await confirm({ + message: `Detected ${label} as ${detectedValue}, is this correct?`, + }); + if (confirmed) { + return detectedValue; + } } // eslint-disable-next-line no-empty } catch (e) {} diff --git a/typescript/cli/src/utils/env.ts b/typescript/cli/src/utils/env.ts index 9a3e74ccd..51ab1ce35 100644 --- a/typescript/cli/src/utils/env.ts +++ b/typescript/cli/src/utils/env.ts @@ -4,6 +4,9 @@ const envScheme = z.object({ HYP_KEY: z.string().optional(), ANVIL_IP_ADDR: z.string().optional(), ANVIL_PORT: z.number().optional(), + AWS_ACCESS_KEY_ID: z.string().optional(), + AWS_SECRET_ACCESS_KEY: z.string().optional(), + AWS_REGION: z.string().optional(), }); const parsedEnv = envScheme.safeParse(process.env); diff --git a/typescript/cli/src/utils/files.ts b/typescript/cli/src/utils/files.ts index 9734b3488..844eb4b79 100644 --- a/typescript/cli/src/utils/files.ts +++ b/typescript/cli/src/utils/files.ts @@ -1,6 +1,7 @@ import { input } from '@inquirer/prompts'; import select from '@inquirer/select'; import fs from 'fs'; +import os from 'os'; import path from 'path'; import { parse as yamlParse, stringify as yamlStringify } from 'yaml'; @@ -15,6 +16,14 @@ export type ArtifactsFile = { description: string; }; +export function resolvePath(filePath: string): string { + if (filePath.startsWith('~')) { + const homedir = os.homedir(); + return path.join(homedir, filePath.slice(1)); + } + return filePath; +} + export function isFile(filepath: string) { if (!filepath) return false; try { diff --git a/typescript/cli/src/validator/address.ts b/typescript/cli/src/validator/address.ts new file mode 100644 index 000000000..d816fcb1f --- /dev/null +++ b/typescript/cli/src/validator/address.ts @@ -0,0 +1,166 @@ +import { GetPublicKeyCommand, KMSClient } from '@aws-sdk/client-kms'; +import { GetObjectCommand, S3Client } from '@aws-sdk/client-s3'; +import { input } from '@inquirer/prompts'; +// @ts-ignore +import asn1 from 'asn1.js'; +import { ethers } from 'ethers'; + +import { assert } from '@hyperlane-xyz/utils'; + +import { CommandContext } from '../context/types.js'; +import { log, logBlue } from '../logger.js'; + +export async function getValidatorAddress({ + context, + accessKey, + secretKey, + region, + bucket, + keyId, +}: { + context: CommandContext; + accessKey?: string; + secretKey?: string; + region?: string; + bucket?: string; + keyId?: string; +}) { + if (!bucket && !keyId) { + throw new Error('Must provide either an S3 bucket or a KMS Key ID.'); + } + + // Query user for AWS parameters if not passed in or stored as .env variables + accessKey ||= await getAccessKeyId(context.skipConfirmation); + secretKey ||= await getSecretAccessKey(context.skipConfirmation); + region ||= await getRegion(context.skipConfirmation); + + assert(accessKey, 'No access key ID set.'); + assert(secretKey, 'No secret access key set.'); + assert(region, 'No AWS region set.'); + + let validatorAddress; + if (bucket) { + validatorAddress = await getAddressFromBucket( + bucket, + accessKey, + secretKey, + region, + ); + } else { + validatorAddress = await getAddressFromKey( + keyId!, + accessKey, + secretKey, + region, + ); + } + + logBlue('Validator address is: '); + log(validatorAddress); +} + +/** + * Displays validator key address from + * validator announcement S3 bucket. + */ +async function getAddressFromBucket( + bucket: string, + accessKeyId: string, + secretAccessKey: string, + region: string, +) { + const s3Client = new S3Client({ + region: region, + credentials: { + accessKeyId, + secretAccessKey, + }, + }); + + const { Body } = await s3Client.send( + new GetObjectCommand({ + Bucket: bucket, + Key: 'announcement.json', + }), + ); + + if (Body) { + const announcement = JSON.parse(await Body?.transformToString()); + return announcement['value']['validator']; + } else { + throw new Error('Announcement file announcement.json not found in bucket'); + } +} + +/** + * Logs validator key address using AWS KMS key ID. + * Taken from github.com/tkporter/get-aws-kms-address/ + */ +async function getAddressFromKey( + keyId: string, + accessKeyId: string, + secretAccessKey: string, + region: string, +) { + const client = new KMSClient({ + region: region, + credentials: { + accessKeyId, + secretAccessKey, + }, + }); + + const publicKeyResponse = await client.send( + new GetPublicKeyCommand({ KeyId: keyId }), + ); + + return getEthereumAddress(Buffer.from(publicKeyResponse.PublicKey!)); +} + +const EcdsaPubKey = asn1.define('EcdsaPubKey', function (this: any) { + this.seq().obj( + this.key('algo').seq().obj(this.key('a').objid(), this.key('b').objid()), + this.key('pubKey').bitstr(), + ); +}); + +function getEthereumAddress(publicKey: Buffer): string { + // The public key is ASN1 encoded in a format according to + // https://tools.ietf.org/html/rfc5480#section-2 + const res = EcdsaPubKey.decode(publicKey, 'der'); + let pubKeyBuffer: Buffer = res.pubKey.data; + + // The public key starts with a 0x04 prefix that needs to be removed + // more info: https://www.oreilly.com/library/view/mastering-ethereum/9781491971932/ch04.html + pubKeyBuffer = pubKeyBuffer.slice(1, pubKeyBuffer.length); + + const address = ethers.utils.keccak256(pubKeyBuffer); // keccak256 hash of publicKey + return `0x${address.slice(-40)}`; // take last 20 bytes as ethereum address +} + +async function getAccessKeyId(skipConfirmation: boolean) { + if (skipConfirmation) throw new Error('No AWS access key ID set.'); + else + return await input({ + message: + 'Please enter AWS access key ID or use the AWS_ACCESS_KEY_ID environment variable.', + }); +} + +async function getSecretAccessKey(skipConfirmation: boolean) { + if (skipConfirmation) throw new Error('No AWS secret access key set.'); + else + return await input({ + message: + 'Please enter AWS secret access key or use the AWS_SECRET_ACCESS_KEY environment variable.', + }); +} + +async function getRegion(skipConfirmation: boolean) { + if (skipConfirmation) throw new Error('No AWS region set.'); + else + return await input({ + message: + 'Please enter AWS region or use the AWS_REGION environment variable.', + }); +} diff --git a/typescript/cli/src/version.ts b/typescript/cli/src/version.ts index f1447b455..9fb80ae08 100644 --- a/typescript/cli/src/version.ts +++ b/typescript/cli/src/version.ts @@ -1 +1 @@ -export const VERSION = '3.12.2'; +export const VERSION = '3.13.0'; diff --git a/typescript/helloworld/CHANGELOG.md b/typescript/helloworld/CHANGELOG.md index 979fb8285..f5e8404be 100644 --- a/typescript/helloworld/CHANGELOG.md +++ b/typescript/helloworld/CHANGELOG.md @@ -1,5 +1,17 @@ # @hyperlane-xyz/helloworld +## 3.13.0 + +### Patch Changes + +- b6b26e2bb: fix: minor change was breaking in registry export +- Updated dependencies [39ea7cdef] +- Updated dependencies [babe816f8] +- Updated dependencies [b440d98be] +- Updated dependencies [0cf692e73] + - @hyperlane-xyz/sdk@3.13.0 + - @hyperlane-xyz/core@3.13.0 + ## 3.12.0 ### Patch Changes diff --git a/typescript/helloworld/package.json b/typescript/helloworld/package.json index f577cf8d8..7d3f62b3d 100644 --- a/typescript/helloworld/package.json +++ b/typescript/helloworld/package.json @@ -1,11 +1,11 @@ { "name": "@hyperlane-xyz/helloworld", "description": "A basic skeleton of an Hyperlane app", - "version": "3.12.2", + "version": "3.13.0", "dependencies": { - "@hyperlane-xyz/core": "3.12.2", - "@hyperlane-xyz/registry": "^1.0.7", - "@hyperlane-xyz/sdk": "3.12.2", + "@hyperlane-xyz/core": "3.13.0", + "@hyperlane-xyz/registry": "1.3.0", + "@hyperlane-xyz/sdk": "3.13.0", "@openzeppelin/contracts-upgradeable": "^4.9.3", "ethers": "^5.7.2" }, diff --git a/typescript/infra/CHANGELOG.md b/typescript/infra/CHANGELOG.md index 21b608413..0696269d8 100644 --- a/typescript/infra/CHANGELOG.md +++ b/typescript/infra/CHANGELOG.md @@ -1,5 +1,23 @@ # @hyperlane-xyz/infra +## 3.13.0 + +### Minor Changes + +- 39ea7cdef: Implement multi collateral warp routes +- 0cf692e73: Implement metadata builder fetching from message + +### Patch Changes + +- b6b26e2bb: fix: minor change was breaking in registry export +- Updated dependencies [b6b26e2bb] +- Updated dependencies [39ea7cdef] +- Updated dependencies [babe816f8] +- Updated dependencies [0cf692e73] + - @hyperlane-xyz/helloworld@3.13.0 + - @hyperlane-xyz/sdk@3.13.0 + - @hyperlane-xyz/utils@3.13.0 + ## 3.12.0 ### Patch Changes diff --git a/typescript/infra/config/environments/mainnet3/agent.ts b/typescript/infra/config/environments/mainnet3/agent.ts index 2b4d53e57..971e30530 100644 --- a/typescript/infra/config/environments/mainnet3/agent.ts +++ b/typescript/infra/config/environments/mainnet3/agent.ts @@ -53,18 +53,20 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig = { bsc: true, celo: true, ethereum: true, - neutron: true, + gnosis: true, + injective: true, + inevm: true, mantapacific: true, mode: true, moonbeam: true, + neutron: true, optimism: true, polygon: true, - gnosis: true, - scroll: true, polygonzkevm: true, - injective: true, - inevm: true, + redstone: true, + scroll: true, viction: true, + zetachain: true, }, [Role.Relayer]: { arbitrum: true, @@ -75,19 +77,21 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig = { bsc: true, celo: true, ethereum: true, - // At the moment, we only relay between Neutron and Manta Pacific on the neutron context. - neutron: false, + gnosis: true, + injective: true, + inevm: true, mantapacific: true, mode: true, moonbeam: true, + // At the moment, we only relay between Neutron and Manta Pacific on the neutron context. + neutron: false, optimism: true, polygon: true, - gnosis: true, - scroll: true, polygonzkevm: true, - injective: true, - inevm: true, + redstone: true, + scroll: true, viction: true, + zetachain: true, }, [Role.Scraper]: { arbitrum: true, @@ -98,21 +102,23 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig = { bsc: true, celo: true, ethereum: true, + gnosis: true, // Cannot scrape non-EVM chains - neutron: false, + injective: false, + inevm: true, mantapacific: true, mode: true, moonbeam: true, + // Cannot scrape non-EVM chains + neutron: false, optimism: true, polygon: true, - gnosis: true, - scroll: true, polygonzkevm: true, - // Cannot scrape non-EVM chains - injective: false, - inevm: true, + redstone: true, + scroll: true, // Has RPC non-compliance that breaks scraping. viction: false, + zetachain: true, }, }; @@ -203,7 +209,7 @@ const hyperlane: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: 'c9c5d37-20240510-014327', + tag: 'd6bb976-20240520-164138', }, gasPaymentEnforcement: gasPaymentEnforcement, metricAppContexts, @@ -211,7 +217,7 @@ const hyperlane: RootAgentConfig = { validators: { docker: { repo, - tag: 'c9c5d37-20240510-014327', + tag: 'de8c2a7-20240515-135254', }, rpcConsensusType: RpcConsensusType.Quorum, chains: validatorChainConfig(Contexts.Hyperlane), @@ -220,7 +226,7 @@ const hyperlane: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: 'c9c5d37-20240510-014327', + tag: 'd6bb976-20240520-164138', }, }, }; diff --git a/typescript/infra/config/environments/mainnet3/aw-validators/hyperlane.json b/typescript/infra/config/environments/mainnet3/aw-validators/hyperlane.json index 550918b8d..a51936232 100644 --- a/typescript/infra/config/environments/mainnet3/aw-validators/hyperlane.json +++ b/typescript/infra/config/environments/mainnet3/aw-validators/hyperlane.json @@ -109,6 +109,9 @@ "0x6a1da2e0b7ae26aaece1377c0a4dbe25b85fa3ca" ] }, + "redstone": { + "validators": ["0x1400b9737007f7978d8b4bbafb4a69c83f0641a7"] + }, "scroll": { "validators": [ "0xad557170a9f2f21c35e03de07cb30dcbcc3dff63", @@ -118,5 +121,8 @@ }, "viction": { "validators": ["0x1f87c368f8e05a85ef9126d984a980a20930cb9c"] + }, + "zetachain": { + "validators": ["0xa3bca0b80317dbf9c7dce16a16ac89f4ff2b23ef"] } } diff --git a/typescript/infra/config/environments/mainnet3/aw-validators/rc.json b/typescript/infra/config/environments/mainnet3/aw-validators/rc.json index 112dc4a77..31f01e7aa 100644 --- a/typescript/infra/config/environments/mainnet3/aw-validators/rc.json +++ b/typescript/infra/config/environments/mainnet3/aw-validators/rc.json @@ -106,6 +106,9 @@ "0x1cd73544c000fd519784f56e59bc380a5fef53d6" ] }, + "redstone": { + "validators": ["0x51ed7127c0afc0513a0f141e910c5e02b2a9a4b5"] + }, "scroll": { "validators": [ "0x11387d89856219cf685f22781bf4e85e00468d54", @@ -119,5 +122,8 @@ "0xad94659e2383214e4a1c4e8d3c17caffb75bc31b", "0x0f9e5775ac4d3b73dd28e5a3f8394443186cb70c" ] + }, + "zetachain": { + "validators": ["0xa13d146b47242671466e4041f5fe68d22a2ffe09"] } } diff --git a/typescript/infra/config/environments/mainnet3/chains.ts b/typescript/infra/config/environments/mainnet3/chains.ts index 29f33e936..0e5d4144e 100644 --- a/typescript/infra/config/environments/mainnet3/chains.ts +++ b/typescript/infra/config/environments/mainnet3/chains.ts @@ -30,7 +30,7 @@ export const ethereumMainnetConfigs: ChainMap = { transactionOverrides: { // A very high max fee per gas is used as Polygon is susceptible // to large swings in gas prices. - maxFeePerGas: 800 * 10 ** 9, // 800 gwei + maxFeePerGas: 550 * 10 ** 9, // 550 gwei maxPriorityFeePerGas: 50 * 10 ** 9, // 50 gwei }, }, diff --git a/typescript/infra/config/environments/mainnet3/core/verification.json b/typescript/infra/config/environments/mainnet3/core/verification.json index b49623540..aa032b48a 100644 --- a/typescript/infra/config/environments/mainnet3/core/verification.json +++ b/typescript/infra/config/environments/mainnet3/core/verification.json @@ -683,6 +683,12 @@ "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", "isProxy": false, "name": "ValidatorAnnounce" + }, + { + "address": "0xcf678903c003651DB0bb933820259A16ea9d95e4", + "constructorArguments": "000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "isProxy": false, + "name": "PausableIsm" } ], "arbitrum": [ @@ -865,6 +871,18 @@ "constructorArguments": "000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", "isProxy": false, "name": "PausableIsm" + }, + { + "address": "0xecE63bD3561a8d2daF5763804B91a772183793aF", + "constructorArguments": "000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "isProxy": false, + "name": "PausableIsm" + }, + { + "address": "0x1E38556b4fE553e6249448960875883990efcf34", + "constructorArguments": "000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "isProxy": false, + "name": "PausableIsm" } ], "avalanche": [ @@ -1035,6 +1053,12 @@ "constructorArguments": "", "isProxy": false, "name": "PausableHook" + }, + { + "address": "0xd76080269C641e1adb786b72ae60Ddac3b6b8ed0", + "constructorArguments": "000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "isProxy": false, + "name": "PausableIsm" } ], "base": [ @@ -1181,6 +1205,12 @@ "constructorArguments": "", "isProxy": false, "name": "PausableHook" + }, + { + "address": "0x2AF32cF8e3Cf42d221eDa0c843818fA5ee129E27", + "constructorArguments": "000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "isProxy": false, + "name": "PausableIsm" } ], "blast": [ @@ -1945,6 +1975,12 @@ "constructorArguments": "000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", "isProxy": false, "name": "PausableIsm" + }, + { + "address": "0x4C97D35c668EE5194a13c8DE8Afc18cce40C9F28", + "constructorArguments": "000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "isProxy": false, + "name": "PausableIsm" } ], "bsc": [ @@ -1995,6 +2031,12 @@ "constructorArguments": "0000000000000000000000002971b9aec44be4eb673df1b88cdb57b96eefe8a4", "isProxy": false, "name": "ValidatorAnnounce" + }, + { + "address": "0x25dB01caDf91CfD2f7e6dD829Ce81698217F9151", + "constructorArguments": "000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "isProxy": false, + "name": "PausableIsm" } ], "celo": [ @@ -2299,6 +2341,12 @@ "constructorArguments": "000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", "isProxy": false, "name": "PausableIsm" + }, + { + "address": "0xDC98a856fb9112894c2fE32267DA8bF35645FAF3", + "constructorArguments": "000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "isProxy": false, + "name": "PausableIsm" } ], "gnosis": [ @@ -2391,6 +2439,12 @@ "constructorArguments": "", "isProxy": false, "name": "PausableHook" + }, + { + "address": "0x223F7D3f27E6272266AE4B5B91Fd5C7A2d798cD8", + "constructorArguments": "000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "isProxy": false, + "name": "PausableIsm" } ], "inevm": [ @@ -3357,1805 +3411,2593 @@ "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", "isProxy": false, "name": "ValidatorAnnounce" - }, - { - "address": "0x086eF95a2F74582Ee30E7D698518a872fb18301f", - "constructorArguments": "000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", - "isProxy": false, - "name": "PausableIsm" } ], - "mode": [ - { - "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", - "constructorArguments": "", - "isProxy": false, - "name": "ProxyAdmin" - }, + "moonbeam": [ { - "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", - "constructorArguments": "000000000000000000000000000000000000000000000000000000000000868b", + "address": "0xeE064c4Dd3d476676a40b7cab94Ef651444175c0", + "constructorArguments": "0000000000000000000000000000000000000000000000000000000000000504", "isProxy": false, "name": "Mailbox" }, { - "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", - "constructorArguments": "0000000000000000000000004ed7d626f1e96cd1c0401607bf70d95243e3ded10000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "address": "0x094d03E751f49908080EFf000Dd6FD177fd44CC3", + "constructorArguments": "000000000000000000000000ee064c4dd3d476676a40b7cab94ef651444175c00000000000000000000000006a9cda3dd1f593983bfd142eb35e6ce4137bd5ce00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0x61DDB465eEA5bc3708Cf8B53156aC91a77A2f029", - "constructorArguments": "000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", - "isProxy": false, - "name": "PausableIsm" - }, - { - "address": "0xE2ee936bEa8e42671c400aC96dE198E06F2bA2A6", - "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", - "isProxy": false, - "name": "MerkleTreeHook" - }, - { - "address": "0x8F1E22d309baa69D398a03cc88E9b46037e988AA", - "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000e2ee936bea8e42671c400ac96de198e06f2ba2a6", + "address": "0xeE064c4Dd3d476676a40b7cab94Ef651444175c0", + "constructorArguments": "0000000000000000000000000000000000000000000000000000000000000504", "isProxy": false, - "name": "FallbackRoutingHook" + "name": "Mailbox" }, { - "address": "0xA1ac41d8A663fd317cc3BD94C7de92dC4BA4a882", - "constructorArguments": "", - "isProxy": false, - "name": "PausableHook" + "address": "0x094d03E751f49908080EFf000Dd6FD177fd44CC3", + "constructorArguments": "000000000000000000000000ee064c4dd3d476676a40b7cab94ef651444175c00000000000000000000000006a9cda3dd1f593983bfd142eb35e6ce4137bd5ce00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", - "constructorArguments": "", + "address": "0x87403b85f6f316e7ba91ba1fa6C3Fb7dD4095547", + "constructorArguments": "000000000000000000000000094d03e751f49908080eff000dd6fd177fd44cc3", "isProxy": false, - "name": "StorageGasOracle" + "name": "MerkleTreeHook" }, { - "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", + "address": "0x89e8C8735f3C3956168BAd6C31e95ecE19CaF507", "constructorArguments": "", "isProxy": false, "name": "InterchainGasPaymaster" }, { - "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", - "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x14760E32C0746094cF14D97124865BC7F0F7368F", + "constructorArguments": "00000000000000000000000089e8c8735f3c3956168bad6c31e95ece19caf5070000000000000000000000006a9cda3dd1f593983bfd142eb35e6ce4137bd5ce00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", - "constructorArguments": "", + "address": "0x87403b85f6f316e7ba91ba1fa6C3Fb7dD4095547", + "constructorArguments": "000000000000000000000000094d03e751f49908080eff000dd6fd177fd44cc3", "isProxy": false, - "name": "StorageGasOracle" + "name": "MerkleTreeHook" }, { - "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", + "address": "0x89e8C8735f3C3956168BAd6C31e95ecE19CaF507", "constructorArguments": "", "isProxy": false, "name": "InterchainGasPaymaster" }, { - "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", - "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x14760E32C0746094cF14D97124865BC7F0F7368F", + "constructorArguments": "00000000000000000000000089e8c8735f3c3956168bad6c31e95ece19caf5070000000000000000000000006a9cda3dd1f593983bfd142eb35e6ce4137bd5ce00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", - "constructorArguments": "", + "address": "0xCd3e29A9D293DcC7341295996a118913F7c582c0", + "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", "isProxy": false, - "name": "StorageGasOracle" + "name": "ProtocolFee" }, { - "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", - "constructorArguments": "", + "address": "0x8c1001eBee6F25b31863A55EadfF149aF88B356F", + "constructorArguments": "000000000000000000000000094d03e751f49908080eff000dd6fd177fd44cc3", "isProxy": false, - "name": "InterchainGasPaymaster" - }, - { - "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", - "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "name": "TransparentUpgradeableProxy" + "name": "ValidatorAnnounce" }, { - "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", - "constructorArguments": "", + "address": "0x87403b85f6f316e7ba91ba1fa6C3Fb7dD4095547", + "constructorArguments": "000000000000000000000000094d03e751f49908080eff000dd6fd177fd44cc3", "isProxy": false, - "name": "StorageGasOracle" + "name": "MerkleTreeHook" }, { - "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", + "address": "0x89e8C8735f3C3956168BAd6C31e95ecE19CaF507", "constructorArguments": "", "isProxy": false, "name": "InterchainGasPaymaster" }, { - "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", - "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x14760E32C0746094cF14D97124865BC7F0F7368F", + "constructorArguments": "00000000000000000000000089e8c8735f3c3956168bad6c31e95ece19caf5070000000000000000000000006a9cda3dd1f593983bfd142eb35e6ce4137bd5ce00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", - "constructorArguments": "", + "address": "0x87403b85f6f316e7ba91ba1fa6C3Fb7dD4095547", + "constructorArguments": "000000000000000000000000094d03e751f49908080eff000dd6fd177fd44cc3", "isProxy": false, - "name": "StorageGasOracle" + "name": "MerkleTreeHook" }, { - "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", + "address": "0x89e8C8735f3C3956168BAd6C31e95ecE19CaF507", "constructorArguments": "", "isProxy": false, "name": "InterchainGasPaymaster" }, { - "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", - "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x14760E32C0746094cF14D97124865BC7F0F7368F", + "constructorArguments": "00000000000000000000000089e8c8735f3c3956168bad6c31e95ece19caf5070000000000000000000000006a9cda3dd1f593983bfd142eb35e6ce4137bd5ce00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", - "constructorArguments": "", + "address": "0xCd3e29A9D293DcC7341295996a118913F7c582c0", + "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", "isProxy": false, - "name": "StorageGasOracle" + "name": "ProtocolFee" }, { - "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", - "constructorArguments": "", + "address": "0x8c1001eBee6F25b31863A55EadfF149aF88B356F", + "constructorArguments": "000000000000000000000000094d03e751f49908080eff000dd6fd177fd44cc3", "isProxy": false, - "name": "InterchainGasPaymaster" + "name": "ValidatorAnnounce" }, { - "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", - "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "name": "TransparentUpgradeableProxy" + "address": "0x6C2D6eA0969F7Aa0A850CCA88c7BFACa563B2361", + "constructorArguments": "000000000000000000000000094d03e751f49908080eff000dd6fd177fd44cc3000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000087403b85f6f316e7ba91ba1fa6c3fb7dd4095547", + "isProxy": false, + "name": "FallbackRoutingHook" }, { - "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", + "address": "0xe28f2AEEB42ee83CAd068D9A9a449c8b868C137f", "constructorArguments": "", "isProxy": false, - "name": "StorageGasOracle" + "name": "PausableHook" }, { - "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", - "constructorArguments": "", + "address": "0x6C2D6eA0969F7Aa0A850CCA88c7BFACa563B2361", + "constructorArguments": "000000000000000000000000094d03e751f49908080eff000dd6fd177fd44cc3000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000087403b85f6f316e7ba91ba1fa6c3fb7dd4095547", "isProxy": false, - "name": "InterchainGasPaymaster" + "name": "FallbackRoutingHook" }, { - "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", - "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "name": "TransparentUpgradeableProxy" + "address": "0xe28f2AEEB42ee83CAd068D9A9a449c8b868C137f", + "constructorArguments": "", + "isProxy": false, + "name": "PausableHook" }, { - "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", - "constructorArguments": "", + "address": "0x6C2D6eA0969F7Aa0A850CCA88c7BFACa563B2361", + "constructorArguments": "000000000000000000000000094d03e751f49908080eff000dd6fd177fd44cc3000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000087403b85f6f316e7ba91ba1fa6c3fb7dd4095547", "isProxy": false, - "name": "StorageGasOracle" + "name": "FallbackRoutingHook" }, { - "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", + "address": "0xe28f2AEEB42ee83CAd068D9A9a449c8b868C137f", "constructorArguments": "", "isProxy": false, - "name": "InterchainGasPaymaster" + "name": "PausableHook" }, { - "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", - "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "name": "TransparentUpgradeableProxy" + "address": "0x6C2D6eA0969F7Aa0A850CCA88c7BFACa563B2361", + "constructorArguments": "000000000000000000000000094d03e751f49908080eff000dd6fd177fd44cc3000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000087403b85f6f316e7ba91ba1fa6c3fb7dd4095547", + "isProxy": false, + "name": "FallbackRoutingHook" }, { - "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", + "address": "0xe28f2AEEB42ee83CAd068D9A9a449c8b868C137f", "constructorArguments": "", "isProxy": false, - "name": "StorageGasOracle" - }, + "name": "PausableHook" + } + ], + "optimism": [ { - "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", - "constructorArguments": "", + "address": "0xF00824861e4bFe5dFC769295A50006BA203BBc29", + "constructorArguments": "000000000000000000000000000000000000000000000000000000000000000a", "isProxy": false, - "name": "InterchainGasPaymaster" + "name": "Mailbox" }, { - "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", - "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0xd4C1905BB1D26BC93DAC913e13CaCC278CdCC80D", + "constructorArguments": "000000000000000000000000f00824861e4bfe5dfc769295a50006ba203bbc29000000000000000000000000e047cb95fb3b7117989e911c6afb34771183fc3500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", - "constructorArguments": "", + "address": "0x68eE9bec9B4dbB61f69D9D293Ae26a5AACb2e28f", + "constructorArguments": "000000000000000000000000d4c1905bb1d26bc93dac913e13cacc278cdcc80d", "isProxy": false, - "name": "StorageGasOracle" + "name": "MerkleTreeHook" }, { - "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", + "address": "0x9b27988D926673fe99126DF4eed42A4aae8Bc01F", "constructorArguments": "", "isProxy": false, "name": "InterchainGasPaymaster" }, { - "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", - "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0xD8A76C4D91fCbB7Cc8eA795DFDF870E48368995C", + "constructorArguments": "0000000000000000000000009b27988d926673fe99126df4eed42a4aae8bc01f000000000000000000000000e047cb95fb3b7117989e911c6afb34771183fc3500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", - "constructorArguments": "", + "address": "0x68eE9bec9B4dbB61f69D9D293Ae26a5AACb2e28f", + "constructorArguments": "000000000000000000000000d4c1905bb1d26bc93dac913e13cacc278cdcc80d", "isProxy": false, - "name": "StorageGasOracle" + "name": "MerkleTreeHook" }, { - "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", + "address": "0x9b27988D926673fe99126DF4eed42A4aae8Bc01F", "constructorArguments": "", "isProxy": false, "name": "InterchainGasPaymaster" }, { - "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", - "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0xD8A76C4D91fCbB7Cc8eA795DFDF870E48368995C", + "constructorArguments": "0000000000000000000000009b27988d926673fe99126df4eed42a4aae8bc01f000000000000000000000000e047cb95fb3b7117989e911c6afb34771183fc3500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, + { + "address": "0xD71Ff941120e8f935b8b1E2C1eD72F5d140FF458", + "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "isProxy": false, + "name": "ProtocolFee" + }, + { + "address": "0x30f5b08e01808643221528BB2f7953bf2830Ef38", + "constructorArguments": "000000000000000000000000d4c1905bb1d26bc93dac913e13cacc278cdcc80d", + "isProxy": false, + "name": "ValidatorAnnounce" + }, + { + "address": "0xF00824861e4bFe5dFC769295A50006BA203BBc29", + "constructorArguments": "000000000000000000000000000000000000000000000000000000000000000a", + "isProxy": false, + "name": "Mailbox" + }, + { + "address": "0xd4C1905BB1D26BC93DAC913e13CaCC278CdCC80D", + "constructorArguments": "000000000000000000000000f00824861e4bfe5dfc769295a50006ba203bbc29000000000000000000000000e047cb95fb3b7117989e911c6afb34771183fc3500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0x68eE9bec9B4dbB61f69D9D293Ae26a5AACb2e28f", + "constructorArguments": "000000000000000000000000d4c1905bb1d26bc93dac913e13cacc278cdcc80d", + "isProxy": false, + "name": "MerkleTreeHook" + }, + { + "address": "0x9b27988D926673fe99126DF4eed42A4aae8Bc01F", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" + }, + { + "address": "0xD8A76C4D91fCbB7Cc8eA795DFDF870E48368995C", + "constructorArguments": "0000000000000000000000009b27988d926673fe99126df4eed42a4aae8bc01f000000000000000000000000e047cb95fb3b7117989e911c6afb34771183fc3500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0x68eE9bec9B4dbB61f69D9D293Ae26a5AACb2e28f", + "constructorArguments": "000000000000000000000000d4c1905bb1d26bc93dac913e13cacc278cdcc80d", + "isProxy": false, + "name": "MerkleTreeHook" + }, + { + "address": "0x9b27988D926673fe99126DF4eed42A4aae8Bc01F", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" + }, + { + "address": "0xD8A76C4D91fCbB7Cc8eA795DFDF870E48368995C", + "constructorArguments": "0000000000000000000000009b27988d926673fe99126df4eed42a4aae8bc01f000000000000000000000000e047cb95fb3b7117989e911c6afb34771183fc3500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0xD71Ff941120e8f935b8b1E2C1eD72F5d140FF458", + "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "isProxy": false, + "name": "ProtocolFee" + }, + { + "address": "0x30f5b08e01808643221528BB2f7953bf2830Ef38", + "constructorArguments": "000000000000000000000000d4c1905bb1d26bc93dac913e13cacc278cdcc80d", + "isProxy": false, + "name": "ValidatorAnnounce" + }, + { + "address": "0xD4b132C6d4AA93A4247F1A91e1ED929c0572a43d", + "constructorArguments": "000000000000000000000000d4c1905bb1d26bc93dac913e13cacc278cdcc80d000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000068ee9bec9b4dbb61f69d9d293ae26a5aacb2e28f", + "isProxy": false, + "name": "FallbackRoutingHook" + }, + { + "address": "0xf753CA2269c8A7693ce1808b5709Fbf36a65D47A", + "constructorArguments": "", + "isProxy": false, + "name": "PausableHook" + }, + { + "address": "0xD4b132C6d4AA93A4247F1A91e1ED929c0572a43d", + "constructorArguments": "000000000000000000000000d4c1905bb1d26bc93dac913e13cacc278cdcc80d000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000068ee9bec9b4dbb61f69d9d293ae26a5aacb2e28f", + "isProxy": false, + "name": "FallbackRoutingHook" + }, + { + "address": "0xf753CA2269c8A7693ce1808b5709Fbf36a65D47A", + "constructorArguments": "", + "isProxy": false, + "name": "PausableHook" + }, + { + "address": "0xD4b132C6d4AA93A4247F1A91e1ED929c0572a43d", + "constructorArguments": "000000000000000000000000d4c1905bb1d26bc93dac913e13cacc278cdcc80d000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000068ee9bec9b4dbb61f69d9d293ae26a5aacb2e28f", + "isProxy": false, + "name": "FallbackRoutingHook" + }, + { + "address": "0xf753CA2269c8A7693ce1808b5709Fbf36a65D47A", + "constructorArguments": "", + "isProxy": false, + "name": "PausableHook" + }, + { + "address": "0xD4b132C6d4AA93A4247F1A91e1ED929c0572a43d", + "constructorArguments": "000000000000000000000000d4c1905bb1d26bc93dac913e13cacc278cdcc80d000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000068ee9bec9b4dbb61f69d9d293ae26a5aacb2e28f", + "isProxy": false, + "name": "FallbackRoutingHook" + }, + { + "address": "0xf753CA2269c8A7693ce1808b5709Fbf36a65D47A", + "constructorArguments": "", + "isProxy": false, + "name": "PausableHook" + }, + { + "address": "0xD59a200cCEc5b3b1bF544dD7439De452D718f594", + "constructorArguments": "000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "isProxy": false, + "name": "PausableIsm" + }, + { + "address": "0x33D3803215a32B84BFb6b1627367231EcD6F138F", + "constructorArguments": "000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "isProxy": false, + "name": "PausableIsm" + } + ], + "polygon": [ + { + "address": "0xA3Ae1C7dBAc1C9658708E6aCD271bfB93d87f8A3", + "constructorArguments": "0000000000000000000000000000000000000000000000000000000000000089", + "isProxy": false, + "name": "Mailbox" + }, + { + "address": "0x5d934f4e2f797775e53561bB72aca21ba36B96BB", + "constructorArguments": "000000000000000000000000a3ae1c7dbac1c9658708e6acd271bfb93d87f8a3000000000000000000000000c4f7590c5d30be959225dc75640657954a86b98000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0xA3Ae1C7dBAc1C9658708E6aCD271bfB93d87f8A3", + "constructorArguments": "0000000000000000000000000000000000000000000000000000000000000089", + "isProxy": false, + "name": "Mailbox" + }, + { + "address": "0x5d934f4e2f797775e53561bB72aca21ba36B96BB", + "constructorArguments": "000000000000000000000000a3ae1c7dbac1c9658708e6acd271bfb93d87f8a3000000000000000000000000c4f7590c5d30be959225dc75640657954a86b98000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0xF8F3629e308b4758F8396606405989F8D8C9c578", + "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "isProxy": false, + "name": "ProtocolFee" + }, + { + "address": "0x454E1a1E1CA8B51506090f1b5399083658eA4Fc5", + "constructorArguments": "0000000000000000000000005d934f4e2f797775e53561bb72aca21ba36b96bb", + "isProxy": false, + "name": "ValidatorAnnounce" + }, + { + "address": "0xF8F3629e308b4758F8396606405989F8D8C9c578", + "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "isProxy": false, + "name": "ProtocolFee" + }, + { + "address": "0x454E1a1E1CA8B51506090f1b5399083658eA4Fc5", + "constructorArguments": "0000000000000000000000005d934f4e2f797775e53561bb72aca21ba36b96bb", + "isProxy": false, + "name": "ValidatorAnnounce" + }, + { + "address": "0x6741e91fFDC31c7786E3684427c628dad06299B0", + "constructorArguments": "000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "isProxy": false, + "name": "PausableIsm" + } + ], + "polygonzkevm": [ + { + "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "constructorArguments": "", + "isProxy": false, + "name": "ProxyAdmin" + }, + { + "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", + "constructorArguments": "000000000000000000000000000000000000000000000000000000000000044d", + "isProxy": false, + "name": "Mailbox" + }, + { + "address": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", + "constructorArguments": "000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0x149db7afD694722747035d5AEC7007ccb6F8f112", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", + "isProxy": false, + "name": "MerkleTreeHook" + }, + { + "address": "0x19dc38aeae620380430C200a6E990D5Af5480117", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" + }, + { + "address": "0xBF12ef4B9f307463D3FB59c3604F294dDCe287E2", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" + }, + { + "address": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", + "constructorArguments": "000000000000000000000000bf12ef4b9f307463d3fb59c3604f294ddce287e20000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0x149db7afD694722747035d5AEC7007ccb6F8f112", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", + "isProxy": false, + "name": "MerkleTreeHook" + }, + { + "address": "0x19dc38aeae620380430C200a6E990D5Af5480117", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" + }, + { + "address": "0xBF12ef4B9f307463D3FB59c3604F294dDCe287E2", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" + }, + { + "address": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", + "constructorArguments": "000000000000000000000000bf12ef4b9f307463d3fb59c3604f294ddce287e20000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0xd83A4F747fE80Ed98839e05079B1B7Fe037b1638", + "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "isProxy": false, + "name": "ProtocolFee" + }, + { + "address": "0x2fa5F5C96419C222cDbCeC797D696e6cE428A7A9", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", + "isProxy": false, + "name": "ValidatorAnnounce" + }, + { + "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "constructorArguments": "", + "isProxy": false, + "name": "ProxyAdmin" + }, + { + "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", + "constructorArguments": "000000000000000000000000000000000000000000000000000000000000044d", + "isProxy": false, + "name": "Mailbox" + }, + { + "address": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", + "constructorArguments": "000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0x149db7afD694722747035d5AEC7007ccb6F8f112", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", + "isProxy": false, + "name": "MerkleTreeHook" + }, + { + "address": "0x19dc38aeae620380430C200a6E990D5Af5480117", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" + }, + { + "address": "0xBF12ef4B9f307463D3FB59c3604F294dDCe287E2", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" + }, + { + "address": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", + "constructorArguments": "000000000000000000000000bf12ef4b9f307463d3fb59c3604f294ddce287e20000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0x149db7afD694722747035d5AEC7007ccb6F8f112", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", + "isProxy": false, + "name": "MerkleTreeHook" + }, + { + "address": "0x19dc38aeae620380430C200a6E990D5Af5480117", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" + }, + { + "address": "0xBF12ef4B9f307463D3FB59c3604F294dDCe287E2", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" + }, + { + "address": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", + "constructorArguments": "000000000000000000000000bf12ef4b9f307463d3fb59c3604f294ddce287e20000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0xd83A4F747fE80Ed98839e05079B1B7Fe037b1638", + "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "isProxy": false, + "name": "ProtocolFee" + }, + { + "address": "0x2fa5F5C96419C222cDbCeC797D696e6cE428A7A9", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", + "isProxy": false, + "name": "ValidatorAnnounce" + }, + { + "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, + "name": "PausableHook" + } + ], + "redstone": [ + { + "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", + "constructorArguments": "", + "isProxy": false, + "name": "ProxyAdmin" + }, + { + "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "constructorArguments": "00000000000000000000000000000000000000000000000000000000000002b2", + "isProxy": false, + "name": "Mailbox" + }, + { + "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a70000000000000000000000004ed7d626f1e96cd1c0401607bf70d95243e3ded100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0x79b3D752cc9494eCB93800712471a7a62954C8AE", + "constructorArguments": "000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "isProxy": false, + "name": "PausableIsm" + }, + { + "address": "0x8F1E22d309baa69D398a03cc88E9b46037e988AA", + "constructorArguments": "000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d", + "isProxy": false, + "name": "MerkleTreeHook" + }, + { + "address": "0xA1ac41d8A663fd317cc3BD94C7de92dC4BA4a882", + "constructorArguments": "000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000008f1e22d309baa69d398a03cc88e9b46037e988aa", + "isProxy": false, + "name": "FallbackRoutingHook" + }, { "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", "constructorArguments": "", "isProxy": false, + "name": "PausableHook" + }, + { + "address": "0x6Fae4D9935E2fcb11fC79a64e917fb2BF14DaFaa", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" + }, + { + "address": "0xBDa330Ea8F3005C421C8088e638fBB64fA71b9e0", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" + }, + { + "address": "0x2Fa570E83009eaEef3a1cbd496a9a30F05266634", + "constructorArguments": "000000000000000000000000bda330ea8f3005c421c8088e638fbb64fa71b9e00000000000000000000000004ed7d626f1e96cd1c0401607bf70d95243e3ded100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0x6Fae4D9935E2fcb11fC79a64e917fb2BF14DaFaa", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" + }, + { + "address": "0xBDa330Ea8F3005C421C8088e638fBB64fA71b9e0", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" + }, + { + "address": "0x2Fa570E83009eaEef3a1cbd496a9a30F05266634", + "constructorArguments": "000000000000000000000000bda330ea8f3005c421c8088e638fbb64fa71b9e00000000000000000000000004ed7d626f1e96cd1c0401607bf70d95243e3ded100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0x6Fae4D9935E2fcb11fC79a64e917fb2BF14DaFaa", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" + }, + { + "address": "0xBDa330Ea8F3005C421C8088e638fBB64fA71b9e0", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" + }, + { + "address": "0x2Fa570E83009eaEef3a1cbd496a9a30F05266634", + "constructorArguments": "000000000000000000000000bda330ea8f3005c421c8088e638fbb64fa71b9e00000000000000000000000004ed7d626f1e96cd1c0401607bf70d95243e3ded100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0x6Fae4D9935E2fcb11fC79a64e917fb2BF14DaFaa", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" + }, + { + "address": "0xBDa330Ea8F3005C421C8088e638fBB64fA71b9e0", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" + }, + { + "address": "0x2Fa570E83009eaEef3a1cbd496a9a30F05266634", + "constructorArguments": "000000000000000000000000bda330ea8f3005c421c8088e638fbb64fa71b9e00000000000000000000000004ed7d626f1e96cd1c0401607bf70d95243e3ded100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0x6Fae4D9935E2fcb11fC79a64e917fb2BF14DaFaa", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" + }, + { + "address": "0xBDa330Ea8F3005C421C8088e638fBB64fA71b9e0", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" + }, + { + "address": "0x2Fa570E83009eaEef3a1cbd496a9a30F05266634", + "constructorArguments": "000000000000000000000000bda330ea8f3005c421c8088e638fbb64fa71b9e00000000000000000000000004ed7d626f1e96cd1c0401607bf70d95243e3ded100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0x6Fae4D9935E2fcb11fC79a64e917fb2BF14DaFaa", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" + }, + { + "address": "0xBDa330Ea8F3005C421C8088e638fBB64fA71b9e0", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" + }, + { + "address": "0x2Fa570E83009eaEef3a1cbd496a9a30F05266634", + "constructorArguments": "000000000000000000000000bda330ea8f3005c421c8088e638fbb64fa71b9e00000000000000000000000004ed7d626f1e96cd1c0401607bf70d95243e3ded100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0x6Fae4D9935E2fcb11fC79a64e917fb2BF14DaFaa", + "constructorArguments": "", + "isProxy": false, "name": "StorageGasOracle" }, { - "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", + "address": "0xBDa330Ea8F3005C421C8088e638fBB64fA71b9e0", "constructorArguments": "", "isProxy": false, "name": "InterchainGasPaymaster" }, { - "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", - "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x2Fa570E83009eaEef3a1cbd496a9a30F05266634", + "constructorArguments": "000000000000000000000000bda330ea8f3005c421c8088e638fbb64fa71b9e00000000000000000000000004ed7d626f1e96cd1c0401607bf70d95243e3ded100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", + "address": "0x6Fae4D9935E2fcb11fC79a64e917fb2BF14DaFaa", "constructorArguments": "", "isProxy": false, "name": "StorageGasOracle" }, { - "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", + "address": "0xBDa330Ea8F3005C421C8088e638fBB64fA71b9e0", "constructorArguments": "", "isProxy": false, "name": "InterchainGasPaymaster" }, { - "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", - "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x2Fa570E83009eaEef3a1cbd496a9a30F05266634", + "constructorArguments": "000000000000000000000000bda330ea8f3005c421c8088e638fbb64fa71b9e00000000000000000000000004ed7d626f1e96cd1c0401607bf70d95243e3ded100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", + "address": "0x6Fae4D9935E2fcb11fC79a64e917fb2BF14DaFaa", "constructorArguments": "", "isProxy": false, "name": "StorageGasOracle" }, { - "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", + "address": "0xBDa330Ea8F3005C421C8088e638fBB64fA71b9e0", "constructorArguments": "", "isProxy": false, "name": "InterchainGasPaymaster" }, { - "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", - "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x2Fa570E83009eaEef3a1cbd496a9a30F05266634", + "constructorArguments": "000000000000000000000000bda330ea8f3005c421c8088e638fbb64fa71b9e00000000000000000000000004ed7d626f1e96cd1c0401607bf70d95243e3ded100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", + "address": "0x6Fae4D9935E2fcb11fC79a64e917fb2BF14DaFaa", "constructorArguments": "", "isProxy": false, "name": "StorageGasOracle" }, { - "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", + "address": "0xBDa330Ea8F3005C421C8088e638fBB64fA71b9e0", "constructorArguments": "", "isProxy": false, "name": "InterchainGasPaymaster" }, { - "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", - "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x2Fa570E83009eaEef3a1cbd496a9a30F05266634", + "constructorArguments": "000000000000000000000000bda330ea8f3005c421c8088e638fbb64fa71b9e00000000000000000000000004ed7d626f1e96cd1c0401607bf70d95243e3ded100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", + "address": "0x6Fae4D9935E2fcb11fC79a64e917fb2BF14DaFaa", "constructorArguments": "", "isProxy": false, "name": "StorageGasOracle" }, { - "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", + "address": "0xBDa330Ea8F3005C421C8088e638fBB64fA71b9e0", "constructorArguments": "", "isProxy": false, "name": "InterchainGasPaymaster" }, { - "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", - "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x2Fa570E83009eaEef3a1cbd496a9a30F05266634", + "constructorArguments": "000000000000000000000000bda330ea8f3005c421c8088e638fbb64fa71b9e00000000000000000000000004ed7d626f1e96cd1c0401607bf70d95243e3ded100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", + "address": "0x6Fae4D9935E2fcb11fC79a64e917fb2BF14DaFaa", "constructorArguments": "", "isProxy": false, "name": "StorageGasOracle" }, { - "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", + "address": "0xBDa330Ea8F3005C421C8088e638fBB64fA71b9e0", "constructorArguments": "", "isProxy": false, "name": "InterchainGasPaymaster" }, { - "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", - "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x2Fa570E83009eaEef3a1cbd496a9a30F05266634", + "constructorArguments": "000000000000000000000000bda330ea8f3005c421c8088e638fbb64fa71b9e00000000000000000000000004ed7d626f1e96cd1c0401607bf70d95243e3ded100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", + "address": "0x6Fae4D9935E2fcb11fC79a64e917fb2BF14DaFaa", "constructorArguments": "", "isProxy": false, "name": "StorageGasOracle" }, { - "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", + "address": "0xBDa330Ea8F3005C421C8088e638fBB64fA71b9e0", "constructorArguments": "", "isProxy": false, "name": "InterchainGasPaymaster" }, { - "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", - "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x2Fa570E83009eaEef3a1cbd496a9a30F05266634", + "constructorArguments": "000000000000000000000000bda330ea8f3005c421c8088e638fbb64fa71b9e00000000000000000000000004ed7d626f1e96cd1c0401607bf70d95243e3ded100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", + "address": "0x6Fae4D9935E2fcb11fC79a64e917fb2BF14DaFaa", "constructorArguments": "", "isProxy": false, "name": "StorageGasOracle" }, { - "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", + "address": "0xBDa330Ea8F3005C421C8088e638fBB64fA71b9e0", "constructorArguments": "", "isProxy": false, "name": "InterchainGasPaymaster" }, { - "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", - "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x2Fa570E83009eaEef3a1cbd496a9a30F05266634", + "constructorArguments": "000000000000000000000000bda330ea8f3005c421c8088e638fbb64fa71b9e00000000000000000000000004ed7d626f1e96cd1c0401607bf70d95243e3ded100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0xE2ee936bEa8e42671c400aC96dE198E06F2bA2A6", - "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", + "address": "0x6Fae4D9935E2fcb11fC79a64e917fb2BF14DaFaa", + "constructorArguments": "", "isProxy": false, - "name": "MerkleTreeHook" + "name": "StorageGasOracle" + }, + { + "address": "0xBDa330Ea8F3005C421C8088e638fBB64fA71b9e0", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" + }, + { + "address": "0x2Fa570E83009eaEef3a1cbd496a9a30F05266634", + "constructorArguments": "000000000000000000000000bda330ea8f3005c421c8088e638fbb64fa71b9e00000000000000000000000004ed7d626f1e96cd1c0401607bf70d95243e3ded100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0x6Fae4D9935E2fcb11fC79a64e917fb2BF14DaFaa", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" + }, + { + "address": "0xBDa330Ea8F3005C421C8088e638fBB64fA71b9e0", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" + }, + { + "address": "0x2Fa570E83009eaEef3a1cbd496a9a30F05266634", + "constructorArguments": "000000000000000000000000bda330ea8f3005c421c8088e638fbb64fa71b9e00000000000000000000000004ed7d626f1e96cd1c0401607bf70d95243e3ded100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0x6Fae4D9935E2fcb11fC79a64e917fb2BF14DaFaa", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" + }, + { + "address": "0xBDa330Ea8F3005C421C8088e638fBB64fA71b9e0", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" + }, + { + "address": "0x2Fa570E83009eaEef3a1cbd496a9a30F05266634", + "constructorArguments": "000000000000000000000000bda330ea8f3005c421c8088e638fbb64fa71b9e00000000000000000000000004ed7d626f1e96cd1c0401607bf70d95243e3ded100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0x6Fae4D9935E2fcb11fC79a64e917fb2BF14DaFaa", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" + }, + { + "address": "0xBDa330Ea8F3005C421C8088e638fBB64fA71b9e0", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" + }, + { + "address": "0x2Fa570E83009eaEef3a1cbd496a9a30F05266634", + "constructorArguments": "000000000000000000000000bda330ea8f3005c421c8088e638fbb64fa71b9e00000000000000000000000004ed7d626f1e96cd1c0401607bf70d95243e3ded100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0x6Fae4D9935E2fcb11fC79a64e917fb2BF14DaFaa", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" + }, + { + "address": "0xBDa330Ea8F3005C421C8088e638fBB64fA71b9e0", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" + }, + { + "address": "0x2Fa570E83009eaEef3a1cbd496a9a30F05266634", + "constructorArguments": "000000000000000000000000bda330ea8f3005c421c8088e638fbb64fa71b9e00000000000000000000000004ed7d626f1e96cd1c0401607bf70d95243e3ded100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { "address": "0x8F1E22d309baa69D398a03cc88E9b46037e988AA", - "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000e2ee936bea8e42671c400ac96de198e06f2ba2a6", + "constructorArguments": "000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d", "isProxy": false, - "name": "FallbackRoutingHook" + "name": "MerkleTreeHook" }, { "address": "0xA1ac41d8A663fd317cc3BD94C7de92dC4BA4a882", + "constructorArguments": "000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000008f1e22d309baa69d398a03cc88e9b46037e988aa", + "isProxy": false, + "name": "FallbackRoutingHook" + }, + { + "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", "constructorArguments": "", "isProxy": false, "name": "PausableHook" }, { - "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", + "address": "0x6Fae4D9935E2fcb11fC79a64e917fb2BF14DaFaa", "constructorArguments": "", "isProxy": false, "name": "StorageGasOracle" }, { - "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", + "address": "0xBDa330Ea8F3005C421C8088e638fBB64fA71b9e0", "constructorArguments": "", "isProxy": false, "name": "InterchainGasPaymaster" }, { - "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", - "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x2Fa570E83009eaEef3a1cbd496a9a30F05266634", + "constructorArguments": "000000000000000000000000bda330ea8f3005c421c8088e638fbb64fa71b9e00000000000000000000000004ed7d626f1e96cd1c0401607bf70d95243e3ded100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", + "address": "0x6Fae4D9935E2fcb11fC79a64e917fb2BF14DaFaa", "constructorArguments": "", "isProxy": false, "name": "StorageGasOracle" }, { - "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", + "address": "0xBDa330Ea8F3005C421C8088e638fBB64fA71b9e0", "constructorArguments": "", "isProxy": false, "name": "InterchainGasPaymaster" }, { - "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", - "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x2Fa570E83009eaEef3a1cbd496a9a30F05266634", + "constructorArguments": "000000000000000000000000bda330ea8f3005c421c8088e638fbb64fa71b9e00000000000000000000000004ed7d626f1e96cd1c0401607bf70d95243e3ded100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", + "address": "0x6Fae4D9935E2fcb11fC79a64e917fb2BF14DaFaa", "constructorArguments": "", "isProxy": false, "name": "StorageGasOracle" }, { - "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", + "address": "0xBDa330Ea8F3005C421C8088e638fBB64fA71b9e0", "constructorArguments": "", "isProxy": false, "name": "InterchainGasPaymaster" }, { - "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", - "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x2Fa570E83009eaEef3a1cbd496a9a30F05266634", + "constructorArguments": "000000000000000000000000bda330ea8f3005c421c8088e638fbb64fa71b9e00000000000000000000000004ed7d626f1e96cd1c0401607bf70d95243e3ded100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", + "address": "0x6Fae4D9935E2fcb11fC79a64e917fb2BF14DaFaa", "constructorArguments": "", "isProxy": false, "name": "StorageGasOracle" }, { - "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", + "address": "0xBDa330Ea8F3005C421C8088e638fBB64fA71b9e0", "constructorArguments": "", "isProxy": false, "name": "InterchainGasPaymaster" }, { - "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", - "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x2Fa570E83009eaEef3a1cbd496a9a30F05266634", + "constructorArguments": "000000000000000000000000bda330ea8f3005c421c8088e638fbb64fa71b9e00000000000000000000000004ed7d626f1e96cd1c0401607bf70d95243e3ded100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", + "address": "0x6Fae4D9935E2fcb11fC79a64e917fb2BF14DaFaa", "constructorArguments": "", "isProxy": false, "name": "StorageGasOracle" }, { - "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", + "address": "0xBDa330Ea8F3005C421C8088e638fBB64fA71b9e0", "constructorArguments": "", "isProxy": false, "name": "InterchainGasPaymaster" }, { - "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", - "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x2Fa570E83009eaEef3a1cbd496a9a30F05266634", + "constructorArguments": "000000000000000000000000bda330ea8f3005c421c8088e638fbb64fa71b9e00000000000000000000000004ed7d626f1e96cd1c0401607bf70d95243e3ded100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", + "address": "0x6Fae4D9935E2fcb11fC79a64e917fb2BF14DaFaa", "constructorArguments": "", "isProxy": false, "name": "StorageGasOracle" }, { - "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", + "address": "0xBDa330Ea8F3005C421C8088e638fBB64fA71b9e0", "constructorArguments": "", "isProxy": false, "name": "InterchainGasPaymaster" }, { - "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", - "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x2Fa570E83009eaEef3a1cbd496a9a30F05266634", + "constructorArguments": "000000000000000000000000bda330ea8f3005c421c8088e638fbb64fa71b9e00000000000000000000000004ed7d626f1e96cd1c0401607bf70d95243e3ded100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", + "address": "0x6Fae4D9935E2fcb11fC79a64e917fb2BF14DaFaa", "constructorArguments": "", "isProxy": false, "name": "StorageGasOracle" }, { - "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", + "address": "0xBDa330Ea8F3005C421C8088e638fBB64fA71b9e0", "constructorArguments": "", "isProxy": false, "name": "InterchainGasPaymaster" }, { - "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", - "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x2Fa570E83009eaEef3a1cbd496a9a30F05266634", + "constructorArguments": "000000000000000000000000bda330ea8f3005c421c8088e638fbb64fa71b9e00000000000000000000000004ed7d626f1e96cd1c0401607bf70d95243e3ded100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", + "address": "0x6Fae4D9935E2fcb11fC79a64e917fb2BF14DaFaa", "constructorArguments": "", "isProxy": false, "name": "StorageGasOracle" }, { - "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", + "address": "0xBDa330Ea8F3005C421C8088e638fBB64fA71b9e0", "constructorArguments": "", "isProxy": false, "name": "InterchainGasPaymaster" }, { - "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", - "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x2Fa570E83009eaEef3a1cbd496a9a30F05266634", + "constructorArguments": "000000000000000000000000bda330ea8f3005c421c8088e638fbb64fa71b9e00000000000000000000000004ed7d626f1e96cd1c0401607bf70d95243e3ded100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", + "address": "0x6Fae4D9935E2fcb11fC79a64e917fb2BF14DaFaa", "constructorArguments": "", "isProxy": false, "name": "StorageGasOracle" }, { - "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", + "address": "0xBDa330Ea8F3005C421C8088e638fBB64fA71b9e0", "constructorArguments": "", "isProxy": false, "name": "InterchainGasPaymaster" }, { - "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", - "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x2Fa570E83009eaEef3a1cbd496a9a30F05266634", + "constructorArguments": "000000000000000000000000bda330ea8f3005c421c8088e638fbb64fa71b9e00000000000000000000000004ed7d626f1e96cd1c0401607bf70d95243e3ded100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", + "address": "0x6Fae4D9935E2fcb11fC79a64e917fb2BF14DaFaa", "constructorArguments": "", "isProxy": false, "name": "StorageGasOracle" }, { - "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", + "address": "0xBDa330Ea8F3005C421C8088e638fBB64fA71b9e0", "constructorArguments": "", "isProxy": false, "name": "InterchainGasPaymaster" }, { - "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", - "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x2Fa570E83009eaEef3a1cbd496a9a30F05266634", + "constructorArguments": "000000000000000000000000bda330ea8f3005c421c8088e638fbb64fa71b9e00000000000000000000000004ed7d626f1e96cd1c0401607bf70d95243e3ded100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", + "address": "0x6Fae4D9935E2fcb11fC79a64e917fb2BF14DaFaa", "constructorArguments": "", "isProxy": false, "name": "StorageGasOracle" }, { - "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", + "address": "0xBDa330Ea8F3005C421C8088e638fBB64fA71b9e0", "constructorArguments": "", "isProxy": false, "name": "InterchainGasPaymaster" }, { - "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", - "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x2Fa570E83009eaEef3a1cbd496a9a30F05266634", + "constructorArguments": "000000000000000000000000bda330ea8f3005c421c8088e638fbb64fa71b9e00000000000000000000000004ed7d626f1e96cd1c0401607bf70d95243e3ded100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", + "address": "0x6Fae4D9935E2fcb11fC79a64e917fb2BF14DaFaa", "constructorArguments": "", "isProxy": false, "name": "StorageGasOracle" }, { - "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", + "address": "0xBDa330Ea8F3005C421C8088e638fBB64fA71b9e0", "constructorArguments": "", "isProxy": false, "name": "InterchainGasPaymaster" }, { - "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", - "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x2Fa570E83009eaEef3a1cbd496a9a30F05266634", + "constructorArguments": "000000000000000000000000bda330ea8f3005c421c8088e638fbb64fa71b9e00000000000000000000000004ed7d626f1e96cd1c0401607bf70d95243e3ded100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", + "address": "0x6Fae4D9935E2fcb11fC79a64e917fb2BF14DaFaa", "constructorArguments": "", "isProxy": false, "name": "StorageGasOracle" }, { - "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", + "address": "0xBDa330Ea8F3005C421C8088e638fBB64fA71b9e0", "constructorArguments": "", "isProxy": false, "name": "InterchainGasPaymaster" }, { - "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", - "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x2Fa570E83009eaEef3a1cbd496a9a30F05266634", + "constructorArguments": "000000000000000000000000bda330ea8f3005c421c8088e638fbb64fa71b9e00000000000000000000000004ed7d626f1e96cd1c0401607bf70d95243e3ded100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", + "address": "0x6Fae4D9935E2fcb11fC79a64e917fb2BF14DaFaa", "constructorArguments": "", "isProxy": false, "name": "StorageGasOracle" }, { - "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", + "address": "0xBDa330Ea8F3005C421C8088e638fBB64fA71b9e0", "constructorArguments": "", "isProxy": false, "name": "InterchainGasPaymaster" }, { - "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", - "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x2Fa570E83009eaEef3a1cbd496a9a30F05266634", + "constructorArguments": "000000000000000000000000bda330ea8f3005c421c8088e638fbb64fa71b9e00000000000000000000000004ed7d626f1e96cd1c0401607bf70d95243e3ded100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", + "address": "0x6Fae4D9935E2fcb11fC79a64e917fb2BF14DaFaa", "constructorArguments": "", "isProxy": false, "name": "StorageGasOracle" }, { - "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", + "address": "0xBDa330Ea8F3005C421C8088e638fBB64fA71b9e0", "constructorArguments": "", "isProxy": false, "name": "InterchainGasPaymaster" }, { - "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", - "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x2Fa570E83009eaEef3a1cbd496a9a30F05266634", + "constructorArguments": "000000000000000000000000bda330ea8f3005c421c8088e638fbb64fa71b9e00000000000000000000000004ed7d626f1e96cd1c0401607bf70d95243e3ded100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", + "address": "0x6Fae4D9935E2fcb11fC79a64e917fb2BF14DaFaa", "constructorArguments": "", "isProxy": false, "name": "StorageGasOracle" }, { - "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", + "address": "0xBDa330Ea8F3005C421C8088e638fBB64fA71b9e0", "constructorArguments": "", "isProxy": false, "name": "InterchainGasPaymaster" }, { - "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", - "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x2Fa570E83009eaEef3a1cbd496a9a30F05266634", + "constructorArguments": "000000000000000000000000bda330ea8f3005c421c8088e638fbb64fa71b9e00000000000000000000000004ed7d626f1e96cd1c0401607bf70d95243e3ded100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", + "address": "0x6Fae4D9935E2fcb11fC79a64e917fb2BF14DaFaa", "constructorArguments": "", "isProxy": false, "name": "StorageGasOracle" }, { - "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", + "address": "0xBDa330Ea8F3005C421C8088e638fBB64fA71b9e0", "constructorArguments": "", "isProxy": false, "name": "InterchainGasPaymaster" }, { - "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", - "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x2Fa570E83009eaEef3a1cbd496a9a30F05266634", + "constructorArguments": "000000000000000000000000bda330ea8f3005c421c8088e638fbb64fa71b9e00000000000000000000000004ed7d626f1e96cd1c0401607bf70d95243e3ded100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", + "address": "0x6Fae4D9935E2fcb11fC79a64e917fb2BF14DaFaa", "constructorArguments": "", "isProxy": false, "name": "StorageGasOracle" }, { - "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", + "address": "0xBDa330Ea8F3005C421C8088e638fBB64fA71b9e0", "constructorArguments": "", "isProxy": false, "name": "InterchainGasPaymaster" }, { - "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", - "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x2Fa570E83009eaEef3a1cbd496a9a30F05266634", + "constructorArguments": "000000000000000000000000bda330ea8f3005c421c8088e638fbb64fa71b9e00000000000000000000000004ed7d626f1e96cd1c0401607bf70d95243e3ded100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", + "address": "0x6Fae4D9935E2fcb11fC79a64e917fb2BF14DaFaa", "constructorArguments": "", "isProxy": false, "name": "StorageGasOracle" }, { - "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", + "address": "0xBDa330Ea8F3005C421C8088e638fBB64fA71b9e0", "constructorArguments": "", "isProxy": false, "name": "InterchainGasPaymaster" }, { - "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", - "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x2Fa570E83009eaEef3a1cbd496a9a30F05266634", + "constructorArguments": "000000000000000000000000bda330ea8f3005c421c8088e638fbb64fa71b9e00000000000000000000000004ed7d626f1e96cd1c0401607bf70d95243e3ded100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0xea820f9BCFD5E16a0dd42071EB61A29874Ad81A4", + "address": "0x26f32245fCF5Ad53159E875d5Cae62aEcf19c2d4", "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", "isProxy": false, "name": "ProtocolFee" }, { - "address": "0x48083C69f5a42c6B69ABbAd48AE195BD36770ee2", - "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", + "address": "0x12582c7B0f43c6A667CBaA7fA8b112F7fb1E69F0", + "constructorArguments": "000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d", "isProxy": false, "name": "ValidatorAnnounce" } ], - "moonbeam": [ + "scroll": [ { - "address": "0xeE064c4Dd3d476676a40b7cab94Ef651444175c0", - "constructorArguments": "0000000000000000000000000000000000000000000000000000000000000504", + "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "constructorArguments": "", "isProxy": false, - "name": "Mailbox" - }, - { - "address": "0x094d03E751f49908080EFf000Dd6FD177fd44CC3", - "constructorArguments": "000000000000000000000000ee064c4dd3d476676a40b7cab94ef651444175c00000000000000000000000006a9cda3dd1f593983bfd142eb35e6ce4137bd5ce00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "name": "TransparentUpgradeableProxy" + "name": "ProxyAdmin" }, { - "address": "0xeE064c4Dd3d476676a40b7cab94Ef651444175c0", - "constructorArguments": "0000000000000000000000000000000000000000000000000000000000000504", + "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", + "constructorArguments": "0000000000000000000000000000000000000000000000000000000000082750", "isProxy": false, "name": "Mailbox" }, { - "address": "0x094d03E751f49908080EFf000Dd6FD177fd44CC3", - "constructorArguments": "000000000000000000000000ee064c4dd3d476676a40b7cab94ef651444175c00000000000000000000000006a9cda3dd1f593983bfd142eb35e6ce4137bd5ce00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "constructorArguments": "0000000000000000000000004ed7d626f1e96cd1c0401607bf70d95243e3ded10000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0x87403b85f6f316e7ba91ba1fa6C3Fb7dD4095547", - "constructorArguments": "000000000000000000000000094d03e751f49908080eff000dd6fd177fd44cc3", + "address": "0x6119E37Bd66406A1Db74920aC79C15fB8411Ba76", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", "isProxy": false, "name": "MerkleTreeHook" }, { - "address": "0x89e8C8735f3C3956168BAd6C31e95ecE19CaF507", + "address": "0x481171eb1aad17eDE6a56005B7F1aB00C581ef13", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" + }, + { + "address": "0x149db7afD694722747035d5AEC7007ccb6F8f112", "constructorArguments": "", "isProxy": false, "name": "InterchainGasPaymaster" }, { - "address": "0x14760E32C0746094cF14D97124865BC7F0F7368F", - "constructorArguments": "00000000000000000000000089e8c8735f3c3956168bad6c31e95ece19caf5070000000000000000000000006a9cda3dd1f593983bfd142eb35e6ce4137bd5ce00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0xBF12ef4B9f307463D3FB59c3604F294dDCe287E2", + "constructorArguments": "000000000000000000000000149db7afd694722747035d5aec7007ccb6f8f1120000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0x87403b85f6f316e7ba91ba1fa6C3Fb7dD4095547", - "constructorArguments": "000000000000000000000000094d03e751f49908080eff000dd6fd177fd44cc3", + "address": "0x6119E37Bd66406A1Db74920aC79C15fB8411Ba76", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", "isProxy": false, "name": "MerkleTreeHook" }, { - "address": "0x89e8C8735f3C3956168BAd6C31e95ecE19CaF507", + "address": "0x481171eb1aad17eDE6a56005B7F1aB00C581ef13", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" + }, + { + "address": "0x149db7afD694722747035d5AEC7007ccb6F8f112", "constructorArguments": "", "isProxy": false, "name": "InterchainGasPaymaster" }, { - "address": "0x14760E32C0746094cF14D97124865BC7F0F7368F", - "constructorArguments": "00000000000000000000000089e8c8735f3c3956168bad6c31e95ece19caf5070000000000000000000000006a9cda3dd1f593983bfd142eb35e6ce4137bd5ce00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0xBF12ef4B9f307463D3FB59c3604F294dDCe287E2", + "constructorArguments": "000000000000000000000000149db7afd694722747035d5aec7007ccb6f8f1120000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0xCd3e29A9D293DcC7341295996a118913F7c582c0", + "address": "0xc3F23848Ed2e04C0c6d41bd7804fa8f89F940B94", "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", "isProxy": false, "name": "ProtocolFee" }, { - "address": "0x8c1001eBee6F25b31863A55EadfF149aF88B356F", - "constructorArguments": "000000000000000000000000094d03e751f49908080eff000dd6fd177fd44cc3", + "address": "0xd83A4F747fE80Ed98839e05079B1B7Fe037b1638", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", "isProxy": false, "name": "ValidatorAnnounce" }, { - "address": "0x87403b85f6f316e7ba91ba1fa6C3Fb7dD4095547", - "constructorArguments": "000000000000000000000000094d03e751f49908080eff000dd6fd177fd44cc3", + "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "constructorArguments": "", + "isProxy": false, + "name": "ProxyAdmin" + }, + { + "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", + "constructorArguments": "0000000000000000000000000000000000000000000000000000000000082750", + "isProxy": false, + "name": "Mailbox" + }, + { + "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "constructorArguments": "0000000000000000000000004ed7d626f1e96cd1c0401607bf70d95243e3ded10000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0x6119E37Bd66406A1Db74920aC79C15fB8411Ba76", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", "isProxy": false, "name": "MerkleTreeHook" }, { - "address": "0x89e8C8735f3C3956168BAd6C31e95ecE19CaF507", + "address": "0x481171eb1aad17eDE6a56005B7F1aB00C581ef13", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" + }, + { + "address": "0x149db7afD694722747035d5AEC7007ccb6F8f112", "constructorArguments": "", "isProxy": false, "name": "InterchainGasPaymaster" }, { - "address": "0x14760E32C0746094cF14D97124865BC7F0F7368F", - "constructorArguments": "00000000000000000000000089e8c8735f3c3956168bad6c31e95ece19caf5070000000000000000000000006a9cda3dd1f593983bfd142eb35e6ce4137bd5ce00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0xBF12ef4B9f307463D3FB59c3604F294dDCe287E2", + "constructorArguments": "000000000000000000000000149db7afd694722747035d5aec7007ccb6f8f1120000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0x87403b85f6f316e7ba91ba1fa6C3Fb7dD4095547", - "constructorArguments": "000000000000000000000000094d03e751f49908080eff000dd6fd177fd44cc3", + "address": "0x6119E37Bd66406A1Db74920aC79C15fB8411Ba76", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", "isProxy": false, "name": "MerkleTreeHook" }, { - "address": "0x89e8C8735f3C3956168BAd6C31e95ecE19CaF507", + "address": "0x481171eb1aad17eDE6a56005B7F1aB00C581ef13", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" + }, + { + "address": "0x149db7afD694722747035d5AEC7007ccb6F8f112", "constructorArguments": "", "isProxy": false, "name": "InterchainGasPaymaster" }, { - "address": "0x14760E32C0746094cF14D97124865BC7F0F7368F", - "constructorArguments": "00000000000000000000000089e8c8735f3c3956168bad6c31e95ece19caf5070000000000000000000000006a9cda3dd1f593983bfd142eb35e6ce4137bd5ce00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0xBF12ef4B9f307463D3FB59c3604F294dDCe287E2", + "constructorArguments": "000000000000000000000000149db7afd694722747035d5aec7007ccb6f8f1120000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0xCd3e29A9D293DcC7341295996a118913F7c582c0", + "address": "0xc3F23848Ed2e04C0c6d41bd7804fa8f89F940B94", "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", "isProxy": false, "name": "ProtocolFee" }, { - "address": "0x8c1001eBee6F25b31863A55EadfF149aF88B356F", - "constructorArguments": "000000000000000000000000094d03e751f49908080eff000dd6fd177fd44cc3", + "address": "0xd83A4F747fE80Ed98839e05079B1B7Fe037b1638", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", "isProxy": false, "name": "ValidatorAnnounce" }, { - "address": "0x6C2D6eA0969F7Aa0A850CCA88c7BFACa563B2361", - "constructorArguments": "000000000000000000000000094d03e751f49908080eff000dd6fd177fd44cc3000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000087403b85f6f316e7ba91ba1fa6c3fb7dd4095547", + "address": "0xDa7cECb05C4aeB02c1aFDE277d4306a2da7Bd762", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000006119e37bd66406a1db74920ac79c15fb8411ba76", "isProxy": false, "name": "FallbackRoutingHook" }, { - "address": "0xe28f2AEEB42ee83CAd068D9A9a449c8b868C137f", + "address": "0x4Eb82Ee35b0a1c1d776E3a3B547f9A9bA6FCC9f2", "constructorArguments": "", "isProxy": false, "name": "PausableHook" }, { - "address": "0x6C2D6eA0969F7Aa0A850CCA88c7BFACa563B2361", - "constructorArguments": "000000000000000000000000094d03e751f49908080eff000dd6fd177fd44cc3000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000087403b85f6f316e7ba91ba1fa6c3fb7dd4095547", + "address": "0xDa7cECb05C4aeB02c1aFDE277d4306a2da7Bd762", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000006119e37bd66406a1db74920ac79c15fb8411ba76", "isProxy": false, "name": "FallbackRoutingHook" }, { - "address": "0xe28f2AEEB42ee83CAd068D9A9a449c8b868C137f", + "address": "0x4Eb82Ee35b0a1c1d776E3a3B547f9A9bA6FCC9f2", "constructorArguments": "", "isProxy": false, "name": "PausableHook" }, { - "address": "0x6C2D6eA0969F7Aa0A850CCA88c7BFACa563B2361", - "constructorArguments": "000000000000000000000000094d03e751f49908080eff000dd6fd177fd44cc3000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000087403b85f6f316e7ba91ba1fa6c3fb7dd4095547", + "address": "0xDa7cECb05C4aeB02c1aFDE277d4306a2da7Bd762", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000006119e37bd66406a1db74920ac79c15fb8411ba76", "isProxy": false, "name": "FallbackRoutingHook" }, { - "address": "0xe28f2AEEB42ee83CAd068D9A9a449c8b868C137f", + "address": "0x4Eb82Ee35b0a1c1d776E3a3B547f9A9bA6FCC9f2", "constructorArguments": "", "isProxy": false, "name": "PausableHook" }, { - "address": "0x6C2D6eA0969F7Aa0A850CCA88c7BFACa563B2361", - "constructorArguments": "000000000000000000000000094d03e751f49908080eff000dd6fd177fd44cc3000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000087403b85f6f316e7ba91ba1fa6c3fb7dd4095547", + "address": "0xDa7cECb05C4aeB02c1aFDE277d4306a2da7Bd762", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000006119e37bd66406a1db74920ac79c15fb8411ba76", "isProxy": false, "name": "FallbackRoutingHook" }, { - "address": "0xe28f2AEEB42ee83CAd068D9A9a449c8b868C137f", + "address": "0x4Eb82Ee35b0a1c1d776E3a3B547f9A9bA6FCC9f2", "constructorArguments": "", "isProxy": false, "name": "PausableHook" } ], - "optimism": [ + "viction": [ + { + "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "constructorArguments": "", + "isProxy": false, + "name": "ProxyAdmin" + }, + { + "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", + "constructorArguments": "0000000000000000000000000000000000000000000000000000000000000058", + "isProxy": false, + "name": "Mailbox" + }, + { + "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "constructorArguments": "0000000000000000000000004ed7d626f1e96cd1c0401607bf70d95243e3ded10000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0x149db7afD694722747035d5AEC7007ccb6F8f112", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", + "isProxy": false, + "name": "MerkleTreeHook" + }, + { + "address": "0x19dc38aeae620380430C200a6E990D5Af5480117", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" + }, + { + "address": "0xBF12ef4B9f307463D3FB59c3604F294dDCe287E2", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" + }, + { + "address": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", + "constructorArguments": "000000000000000000000000bf12ef4b9f307463d3fb59c3604f294ddce287e20000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0x149db7afD694722747035d5AEC7007ccb6F8f112", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", + "isProxy": false, + "name": "MerkleTreeHook" + }, + { + "address": "0x19dc38aeae620380430C200a6E990D5Af5480117", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" + }, + { + "address": "0xBF12ef4B9f307463D3FB59c3604F294dDCe287E2", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" + }, + { + "address": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", + "constructorArguments": "000000000000000000000000bf12ef4b9f307463d3fb59c3604f294ddce287e20000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0xd83A4F747fE80Ed98839e05079B1B7Fe037b1638", + "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "isProxy": false, + "name": "ProtocolFee" + }, + { + "address": "0x2fa5F5C96419C222cDbCeC797D696e6cE428A7A9", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", + "isProxy": false, + "name": "ValidatorAnnounce" + }, + { + "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "constructorArguments": "", + "isProxy": false, + "name": "ProxyAdmin" + }, { - "address": "0xF00824861e4bFe5dFC769295A50006BA203BBc29", - "constructorArguments": "000000000000000000000000000000000000000000000000000000000000000a", + "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", + "constructorArguments": "0000000000000000000000000000000000000000000000000000000000000058", "isProxy": false, "name": "Mailbox" }, { - "address": "0xd4C1905BB1D26BC93DAC913e13CaCC278CdCC80D", - "constructorArguments": "000000000000000000000000f00824861e4bfe5dfc769295a50006ba203bbc29000000000000000000000000e047cb95fb3b7117989e911c6afb34771183fc3500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "constructorArguments": "0000000000000000000000004ed7d626f1e96cd1c0401607bf70d95243e3ded10000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0x68eE9bec9B4dbB61f69D9D293Ae26a5AACb2e28f", - "constructorArguments": "000000000000000000000000d4c1905bb1d26bc93dac913e13cacc278cdcc80d", + "address": "0x149db7afD694722747035d5AEC7007ccb6F8f112", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", "isProxy": false, "name": "MerkleTreeHook" }, { - "address": "0x9b27988D926673fe99126DF4eed42A4aae8Bc01F", + "address": "0x19dc38aeae620380430C200a6E990D5Af5480117", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" + }, + { + "address": "0xBF12ef4B9f307463D3FB59c3604F294dDCe287E2", "constructorArguments": "", "isProxy": false, "name": "InterchainGasPaymaster" }, { - "address": "0xD8A76C4D91fCbB7Cc8eA795DFDF870E48368995C", - "constructorArguments": "0000000000000000000000009b27988d926673fe99126df4eed42a4aae8bc01f000000000000000000000000e047cb95fb3b7117989e911c6afb34771183fc3500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", + "constructorArguments": "000000000000000000000000bf12ef4b9f307463d3fb59c3604f294ddce287e20000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0x68eE9bec9B4dbB61f69D9D293Ae26a5AACb2e28f", - "constructorArguments": "000000000000000000000000d4c1905bb1d26bc93dac913e13cacc278cdcc80d", + "address": "0x149db7afD694722747035d5AEC7007ccb6F8f112", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", "isProxy": false, "name": "MerkleTreeHook" }, { - "address": "0x9b27988D926673fe99126DF4eed42A4aae8Bc01F", + "address": "0x19dc38aeae620380430C200a6E990D5Af5480117", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" + }, + { + "address": "0xBF12ef4B9f307463D3FB59c3604F294dDCe287E2", "constructorArguments": "", "isProxy": false, "name": "InterchainGasPaymaster" }, { - "address": "0xD8A76C4D91fCbB7Cc8eA795DFDF870E48368995C", - "constructorArguments": "0000000000000000000000009b27988d926673fe99126df4eed42a4aae8bc01f000000000000000000000000e047cb95fb3b7117989e911c6afb34771183fc3500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", + "constructorArguments": "000000000000000000000000bf12ef4b9f307463d3fb59c3604f294ddce287e20000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0xD71Ff941120e8f935b8b1E2C1eD72F5d140FF458", + "address": "0xd83A4F747fE80Ed98839e05079B1B7Fe037b1638", "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", "isProxy": false, "name": "ProtocolFee" }, { - "address": "0x30f5b08e01808643221528BB2f7953bf2830Ef38", - "constructorArguments": "000000000000000000000000d4c1905bb1d26bc93dac913e13cacc278cdcc80d", + "address": "0x2fa5F5C96419C222cDbCeC797D696e6cE428A7A9", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", "isProxy": false, "name": "ValidatorAnnounce" }, { - "address": "0xF00824861e4bFe5dFC769295A50006BA203BBc29", - "constructorArguments": "000000000000000000000000000000000000000000000000000000000000000a", + "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "constructorArguments": "", + "isProxy": false, + "name": "ProxyAdmin" + }, + { + "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", + "constructorArguments": "0000000000000000000000000000000000000000000000000000000000000058", "isProxy": false, "name": "Mailbox" }, { - "address": "0xd4C1905BB1D26BC93DAC913e13CaCC278CdCC80D", - "constructorArguments": "000000000000000000000000f00824861e4bfe5dfc769295a50006ba203bbc29000000000000000000000000e047cb95fb3b7117989e911c6afb34771183fc3500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "constructorArguments": "0000000000000000000000004ed7d626f1e96cd1c0401607bf70d95243e3ded10000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0x68eE9bec9B4dbB61f69D9D293Ae26a5AACb2e28f", - "constructorArguments": "000000000000000000000000d4c1905bb1d26bc93dac913e13cacc278cdcc80d", + "address": "0x149db7afD694722747035d5AEC7007ccb6F8f112", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", "isProxy": false, "name": "MerkleTreeHook" }, { - "address": "0x9b27988D926673fe99126DF4eed42A4aae8Bc01F", + "address": "0x19dc38aeae620380430C200a6E990D5Af5480117", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" + }, + { + "address": "0xBF12ef4B9f307463D3FB59c3604F294dDCe287E2", "constructorArguments": "", "isProxy": false, "name": "InterchainGasPaymaster" }, { - "address": "0xD8A76C4D91fCbB7Cc8eA795DFDF870E48368995C", - "constructorArguments": "0000000000000000000000009b27988d926673fe99126df4eed42a4aae8bc01f000000000000000000000000e047cb95fb3b7117989e911c6afb34771183fc3500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", + "constructorArguments": "000000000000000000000000bf12ef4b9f307463d3fb59c3604f294ddce287e20000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0x68eE9bec9B4dbB61f69D9D293Ae26a5AACb2e28f", - "constructorArguments": "000000000000000000000000d4c1905bb1d26bc93dac913e13cacc278cdcc80d", + "address": "0x149db7afD694722747035d5AEC7007ccb6F8f112", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", "isProxy": false, "name": "MerkleTreeHook" }, { - "address": "0x9b27988D926673fe99126DF4eed42A4aae8Bc01F", + "address": "0x19dc38aeae620380430C200a6E990D5Af5480117", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" + }, + { + "address": "0xBF12ef4B9f307463D3FB59c3604F294dDCe287E2", "constructorArguments": "", "isProxy": false, "name": "InterchainGasPaymaster" }, { - "address": "0xD8A76C4D91fCbB7Cc8eA795DFDF870E48368995C", - "constructorArguments": "0000000000000000000000009b27988d926673fe99126df4eed42a4aae8bc01f000000000000000000000000e047cb95fb3b7117989e911c6afb34771183fc3500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", + "constructorArguments": "000000000000000000000000bf12ef4b9f307463d3fb59c3604f294ddce287e20000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0xD71Ff941120e8f935b8b1E2C1eD72F5d140FF458", + "address": "0xd83A4F747fE80Ed98839e05079B1B7Fe037b1638", "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", "isProxy": false, "name": "ProtocolFee" }, { - "address": "0x30f5b08e01808643221528BB2f7953bf2830Ef38", - "constructorArguments": "000000000000000000000000d4c1905bb1d26bc93dac913e13cacc278cdcc80d", + "address": "0x2fa5F5C96419C222cDbCeC797D696e6cE428A7A9", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", "isProxy": false, "name": "ValidatorAnnounce" }, { - "address": "0xD4b132C6d4AA93A4247F1A91e1ED929c0572a43d", - "constructorArguments": "000000000000000000000000d4c1905bb1d26bc93dac913e13cacc278cdcc80d000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000068ee9bec9b4dbb61f69d9d293ae26a5aacb2e28f", + "address": "0x92cdbF0Ccdf8E93467FA858fb986fa650A02f2A8", + "constructorArguments": "000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "isProxy": false, + "name": "PausableIsm" + }, + { + "address": "0x5d69BC38eF3eDb491c0b7186BEc4eC45c4013f93", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000149db7afd694722747035d5aec7007ccb6f8f112", "isProxy": false, "name": "FallbackRoutingHook" }, { - "address": "0xf753CA2269c8A7693ce1808b5709Fbf36a65D47A", + "address": "0xDab56C5A1EffFdd23f6BD1243E457B1575984Bc6", "constructorArguments": "", "isProxy": false, "name": "PausableHook" }, { - "address": "0xD4b132C6d4AA93A4247F1A91e1ED929c0572a43d", - "constructorArguments": "000000000000000000000000d4c1905bb1d26bc93dac913e13cacc278cdcc80d000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000068ee9bec9b4dbb61f69d9d293ae26a5aacb2e28f", + "address": "0x5d69BC38eF3eDb491c0b7186BEc4eC45c4013f93", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000149db7afd694722747035d5aec7007ccb6f8f112", "isProxy": false, "name": "FallbackRoutingHook" }, { - "address": "0xf753CA2269c8A7693ce1808b5709Fbf36a65D47A", + "address": "0xDab56C5A1EffFdd23f6BD1243E457B1575984Bc6", "constructorArguments": "", "isProxy": false, "name": "PausableHook" + } + ], + "zetachain": [ + { + "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "constructorArguments": "", + "isProxy": false, + "name": "ProxyAdmin" }, { - "address": "0xD4b132C6d4AA93A4247F1A91e1ED929c0572a43d", - "constructorArguments": "000000000000000000000000d4c1905bb1d26bc93dac913e13cacc278cdcc80d000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000068ee9bec9b4dbb61f69d9d293ae26a5aacb2e28f", + "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", + "constructorArguments": "0000000000000000000000000000000000000000000000000000000000001b58", + "isProxy": false, + "name": "Mailbox" + }, + { + "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "constructorArguments": "0000000000000000000000004ed7d626f1e96cd1c0401607bf70d95243e3ded10000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0x61DDB465eEA5bc3708Cf8B53156aC91a77A2f029", + "constructorArguments": "000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "isProxy": false, + "name": "PausableIsm" + }, + { + "address": "0xE2ee936bEa8e42671c400aC96dE198E06F2bA2A6", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", + "isProxy": false, + "name": "MerkleTreeHook" + }, + { + "address": "0x8F1E22d309baa69D398a03cc88E9b46037e988AA", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000e2ee936bea8e42671c400ac96de198e06f2ba2a6", "isProxy": false, "name": "FallbackRoutingHook" }, { - "address": "0xf753CA2269c8A7693ce1808b5709Fbf36a65D47A", + "address": "0xA1ac41d8A663fd317cc3BD94C7de92dC4BA4a882", "constructorArguments": "", "isProxy": false, "name": "PausableHook" }, { - "address": "0xD4b132C6d4AA93A4247F1A91e1ED929c0572a43d", - "constructorArguments": "000000000000000000000000d4c1905bb1d26bc93dac913e13cacc278cdcc80d000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000068ee9bec9b4dbb61f69d9d293ae26a5aacb2e28f", + "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", + "constructorArguments": "", "isProxy": false, - "name": "FallbackRoutingHook" + "name": "StorageGasOracle" }, { - "address": "0xf753CA2269c8A7693ce1808b5709Fbf36a65D47A", + "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", "constructorArguments": "", "isProxy": false, - "name": "PausableHook" + "name": "InterchainGasPaymaster" }, { - "address": "0xD59a200cCEc5b3b1bF544dD7439De452D718f594", - "constructorArguments": "000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", + "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", + "constructorArguments": "", "isProxy": false, - "name": "PausableIsm" + "name": "StorageGasOracle" }, { - "address": "0x33D3803215a32B84BFb6b1627367231EcD6F138F", - "constructorArguments": "000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", + "constructorArguments": "", "isProxy": false, - "name": "PausableIsm" - } - ], - "polygon": [ + "name": "InterchainGasPaymaster" + }, { - "address": "0xA3Ae1C7dBAc1C9658708E6aCD271bfB93d87f8A3", - "constructorArguments": "0000000000000000000000000000000000000000000000000000000000000089", + "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", + "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", + "constructorArguments": "", "isProxy": false, - "name": "Mailbox" + "name": "StorageGasOracle" }, { - "address": "0x5d934f4e2f797775e53561bB72aca21ba36B96BB", - "constructorArguments": "000000000000000000000000a3ae1c7dbac1c9658708e6acd271bfb93d87f8a3000000000000000000000000c4f7590c5d30be959225dc75640657954a86b98000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" + }, + { + "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", + "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0xA3Ae1C7dBAc1C9658708E6aCD271bfB93d87f8A3", - "constructorArguments": "0000000000000000000000000000000000000000000000000000000000000089", + "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", + "constructorArguments": "", "isProxy": false, - "name": "Mailbox" + "name": "StorageGasOracle" }, { - "address": "0x5d934f4e2f797775e53561bB72aca21ba36B96BB", - "constructorArguments": "000000000000000000000000a3ae1c7dbac1c9658708e6acd271bfb93d87f8a3000000000000000000000000c4f7590c5d30be959225dc75640657954a86b98000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" + }, + { + "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", + "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0xF8F3629e308b4758F8396606405989F8D8C9c578", - "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", + "constructorArguments": "", "isProxy": false, - "name": "ProtocolFee" + "name": "StorageGasOracle" }, { - "address": "0x454E1a1E1CA8B51506090f1b5399083658eA4Fc5", - "constructorArguments": "0000000000000000000000005d934f4e2f797775e53561bb72aca21ba36b96bb", + "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", + "constructorArguments": "", "isProxy": false, - "name": "ValidatorAnnounce" + "name": "InterchainGasPaymaster" }, { - "address": "0xF8F3629e308b4758F8396606405989F8D8C9c578", - "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", + "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", + "constructorArguments": "", "isProxy": false, - "name": "ProtocolFee" + "name": "StorageGasOracle" }, { - "address": "0x454E1a1E1CA8B51506090f1b5399083658eA4Fc5", - "constructorArguments": "0000000000000000000000005d934f4e2f797775e53561bb72aca21ba36b96bb", + "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", + "constructorArguments": "", "isProxy": false, - "name": "ValidatorAnnounce" - } - ], - "polygonzkevm": [ + "name": "InterchainGasPaymaster" + }, + { + "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", + "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, { - "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", "constructorArguments": "", "isProxy": false, - "name": "ProxyAdmin" + "name": "StorageGasOracle" }, { - "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", - "constructorArguments": "000000000000000000000000000000000000000000000000000000000000044d", + "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", + "constructorArguments": "", "isProxy": false, - "name": "Mailbox" + "name": "InterchainGasPaymaster" }, { - "address": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", - "constructorArguments": "000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", + "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0x149db7afD694722747035d5AEC7007ccb6F8f112", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", - "isProxy": false, - "name": "MerkleTreeHook" - }, - { - "address": "0x19dc38aeae620380430C200a6E990D5Af5480117", + "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", "constructorArguments": "", "isProxy": false, "name": "StorageGasOracle" }, { - "address": "0xBF12ef4B9f307463D3FB59c3604F294dDCe287E2", + "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", "constructorArguments": "", "isProxy": false, "name": "InterchainGasPaymaster" }, { - "address": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", - "constructorArguments": "000000000000000000000000bf12ef4b9f307463d3fb59c3604f294ddce287e20000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", + "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0x149db7afD694722747035d5AEC7007ccb6F8f112", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", - "isProxy": false, - "name": "MerkleTreeHook" - }, - { - "address": "0x19dc38aeae620380430C200a6E990D5Af5480117", + "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", "constructorArguments": "", "isProxy": false, "name": "StorageGasOracle" }, { - "address": "0xBF12ef4B9f307463D3FB59c3604F294dDCe287E2", + "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", "constructorArguments": "", "isProxy": false, "name": "InterchainGasPaymaster" }, { - "address": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", - "constructorArguments": "000000000000000000000000bf12ef4b9f307463d3fb59c3604f294ddce287e20000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", + "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0xd83A4F747fE80Ed98839e05079B1B7Fe037b1638", - "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", + "constructorArguments": "", "isProxy": false, - "name": "ProtocolFee" + "name": "StorageGasOracle" }, { - "address": "0x2fa5F5C96419C222cDbCeC797D696e6cE428A7A9", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", + "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", + "constructorArguments": "", "isProxy": false, - "name": "ValidatorAnnounce" + "name": "InterchainGasPaymaster" }, { - "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", + "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", "constructorArguments": "", "isProxy": false, - "name": "ProxyAdmin" + "name": "StorageGasOracle" }, { - "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", - "constructorArguments": "000000000000000000000000000000000000000000000000000000000000044d", + "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", + "constructorArguments": "", "isProxy": false, - "name": "Mailbox" + "name": "InterchainGasPaymaster" }, { - "address": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", - "constructorArguments": "000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", + "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0x149db7afD694722747035d5AEC7007ccb6F8f112", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", - "isProxy": false, - "name": "MerkleTreeHook" - }, - { - "address": "0x19dc38aeae620380430C200a6E990D5Af5480117", + "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", "constructorArguments": "", "isProxy": false, "name": "StorageGasOracle" }, { - "address": "0xBF12ef4B9f307463D3FB59c3604F294dDCe287E2", + "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", "constructorArguments": "", "isProxy": false, "name": "InterchainGasPaymaster" }, { - "address": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", - "constructorArguments": "000000000000000000000000bf12ef4b9f307463d3fb59c3604f294ddce287e20000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", + "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0x149db7afD694722747035d5AEC7007ccb6F8f112", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", - "isProxy": false, - "name": "MerkleTreeHook" - }, - { - "address": "0x19dc38aeae620380430C200a6E990D5Af5480117", + "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", "constructorArguments": "", "isProxy": false, "name": "StorageGasOracle" }, { - "address": "0xBF12ef4B9f307463D3FB59c3604F294dDCe287E2", + "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", "constructorArguments": "", "isProxy": false, "name": "InterchainGasPaymaster" }, { - "address": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", - "constructorArguments": "000000000000000000000000bf12ef4b9f307463d3fb59c3604f294ddce287e20000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", + "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0xd83A4F747fE80Ed98839e05079B1B7Fe037b1638", - "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", + "constructorArguments": "", "isProxy": false, - "name": "ProtocolFee" + "name": "StorageGasOracle" }, { - "address": "0x2fa5F5C96419C222cDbCeC797D696e6cE428A7A9", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", + "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", + "constructorArguments": "", "isProxy": false, - "name": "ValidatorAnnounce" + "name": "InterchainGasPaymaster" }, { - "address": "0xc2FbB9411186AB3b1a6AFCCA702D1a80B48b197c", + "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", + "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", "constructorArguments": "", "isProxy": false, - "name": "PausableHook" + "name": "StorageGasOracle" }, { - "address": "0xc2FbB9411186AB3b1a6AFCCA702D1a80B48b197c", + "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", "constructorArguments": "", "isProxy": false, - "name": "PausableHook" + "name": "InterchainGasPaymaster" }, { - "address": "0xc2FbB9411186AB3b1a6AFCCA702D1a80B48b197c", + "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", + "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", "constructorArguments": "", "isProxy": false, - "name": "PausableHook" + "name": "StorageGasOracle" }, { - "address": "0xc2FbB9411186AB3b1a6AFCCA702D1a80B48b197c", + "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", "constructorArguments": "", "isProxy": false, - "name": "PausableHook" + "name": "InterchainGasPaymaster" }, { - "address": "0xc2FbB9411186AB3b1a6AFCCA702D1a80B48b197c", + "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", + "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", "constructorArguments": "", "isProxy": false, - "name": "PausableHook" + "name": "StorageGasOracle" }, { - "address": "0xc2FbB9411186AB3b1a6AFCCA702D1a80B48b197c", + "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", "constructorArguments": "", "isProxy": false, - "name": "PausableHook" - } - ], - "scroll": [ + "name": "InterchainGasPaymaster" + }, { - "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", + "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", "constructorArguments": "", "isProxy": false, - "name": "ProxyAdmin" + "name": "StorageGasOracle" }, { - "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", - "constructorArguments": "0000000000000000000000000000000000000000000000000000000000082750", + "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", + "constructorArguments": "", "isProxy": false, - "name": "Mailbox" + "name": "InterchainGasPaymaster" }, { - "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", - "constructorArguments": "0000000000000000000000004ed7d626f1e96cd1c0401607bf70d95243e3ded10000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", + "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0x6119E37Bd66406A1Db74920aC79C15fB8411Ba76", - "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", - "isProxy": false, - "name": "MerkleTreeHook" - }, - { - "address": "0x481171eb1aad17eDE6a56005B7F1aB00C581ef13", + "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", "constructorArguments": "", "isProxy": false, "name": "StorageGasOracle" }, { - "address": "0x149db7afD694722747035d5AEC7007ccb6F8f112", + "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", "constructorArguments": "", "isProxy": false, "name": "InterchainGasPaymaster" }, { - "address": "0xBF12ef4B9f307463D3FB59c3604F294dDCe287E2", - "constructorArguments": "000000000000000000000000149db7afd694722747035d5aec7007ccb6f8f1120000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", + "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0x6119E37Bd66406A1Db74920aC79C15fB8411Ba76", + "address": "0xE2ee936bEa8e42671c400aC96dE198E06F2bA2A6", "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", "isProxy": false, "name": "MerkleTreeHook" }, { - "address": "0x481171eb1aad17eDE6a56005B7F1aB00C581ef13", + "address": "0x8F1E22d309baa69D398a03cc88E9b46037e988AA", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000e2ee936bea8e42671c400ac96de198e06f2ba2a6", + "isProxy": false, + "name": "FallbackRoutingHook" + }, + { + "address": "0xA1ac41d8A663fd317cc3BD94C7de92dC4BA4a882", + "constructorArguments": "", + "isProxy": false, + "name": "PausableHook" + }, + { + "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", "constructorArguments": "", "isProxy": false, "name": "StorageGasOracle" }, { - "address": "0x149db7afD694722747035d5AEC7007ccb6F8f112", + "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", "constructorArguments": "", "isProxy": false, "name": "InterchainGasPaymaster" }, { - "address": "0xBF12ef4B9f307463D3FB59c3604F294dDCe287E2", - "constructorArguments": "000000000000000000000000149db7afd694722747035d5aec7007ccb6f8f1120000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", + "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0xc3F23848Ed2e04C0c6d41bd7804fa8f89F940B94", - "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", + "constructorArguments": "", "isProxy": false, - "name": "ProtocolFee" + "name": "StorageGasOracle" }, { - "address": "0xd83A4F747fE80Ed98839e05079B1B7Fe037b1638", - "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", + "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", + "constructorArguments": "", "isProxy": false, - "name": "ValidatorAnnounce" + "name": "InterchainGasPaymaster" }, { - "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", - "constructorArguments": "", - "isProxy": false, - "name": "ProxyAdmin" + "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", + "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", - "constructorArguments": "0000000000000000000000000000000000000000000000000000000000082750", + "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", + "constructorArguments": "", "isProxy": false, - "name": "Mailbox" + "name": "StorageGasOracle" }, { - "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", - "constructorArguments": "0000000000000000000000004ed7d626f1e96cd1c0401607bf70d95243e3ded10000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "name": "TransparentUpgradeableProxy" + "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" }, { - "address": "0x6119E37Bd66406A1Db74920aC79C15fB8411Ba76", - "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", - "isProxy": false, - "name": "MerkleTreeHook" + "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", + "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "address": "0x481171eb1aad17eDE6a56005B7F1aB00C581ef13", + "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", "constructorArguments": "", "isProxy": false, "name": "StorageGasOracle" }, { - "address": "0x149db7afD694722747035d5AEC7007ccb6F8f112", + "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", "constructorArguments": "", "isProxy": false, "name": "InterchainGasPaymaster" }, { - "address": "0xBF12ef4B9f307463D3FB59c3604F294dDCe287E2", - "constructorArguments": "000000000000000000000000149db7afd694722747035d5aec7007ccb6f8f1120000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", + "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0x6119E37Bd66406A1Db74920aC79C15fB8411Ba76", - "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", - "isProxy": false, - "name": "MerkleTreeHook" - }, - { - "address": "0x481171eb1aad17eDE6a56005B7F1aB00C581ef13", + "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", "constructorArguments": "", "isProxy": false, "name": "StorageGasOracle" }, { - "address": "0x149db7afD694722747035d5AEC7007ccb6F8f112", + "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", "constructorArguments": "", "isProxy": false, "name": "InterchainGasPaymaster" }, { - "address": "0xBF12ef4B9f307463D3FB59c3604F294dDCe287E2", - "constructorArguments": "000000000000000000000000149db7afd694722747035d5aec7007ccb6f8f1120000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", + "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0xc3F23848Ed2e04C0c6d41bd7804fa8f89F940B94", - "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", + "constructorArguments": "", "isProxy": false, - "name": "ProtocolFee" + "name": "StorageGasOracle" }, { - "address": "0xd83A4F747fE80Ed98839e05079B1B7Fe037b1638", - "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", + "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", + "constructorArguments": "", "isProxy": false, - "name": "ValidatorAnnounce" + "name": "InterchainGasPaymaster" }, { - "address": "0xDa7cECb05C4aeB02c1aFDE277d4306a2da7Bd762", - "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000006119e37bd66406a1db74920ac79c15fb8411ba76", - "isProxy": false, - "name": "FallbackRoutingHook" + "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", + "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "address": "0x4Eb82Ee35b0a1c1d776E3a3B547f9A9bA6FCC9f2", + "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", "constructorArguments": "", "isProxy": false, - "name": "PausableHook" - }, - { - "address": "0xDa7cECb05C4aeB02c1aFDE277d4306a2da7Bd762", - "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000006119e37bd66406a1db74920ac79c15fb8411ba76", - "isProxy": false, - "name": "FallbackRoutingHook" + "name": "StorageGasOracle" }, { - "address": "0x4Eb82Ee35b0a1c1d776E3a3B547f9A9bA6FCC9f2", + "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", "constructorArguments": "", "isProxy": false, - "name": "PausableHook" + "name": "InterchainGasPaymaster" }, { - "address": "0xDa7cECb05C4aeB02c1aFDE277d4306a2da7Bd762", - "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000006119e37bd66406a1db74920ac79c15fb8411ba76", - "isProxy": false, - "name": "FallbackRoutingHook" + "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", + "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" }, { - "address": "0x4Eb82Ee35b0a1c1d776E3a3B547f9A9bA6FCC9f2", + "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", "constructorArguments": "", "isProxy": false, - "name": "PausableHook" + "name": "StorageGasOracle" }, { - "address": "0xDa7cECb05C4aeB02c1aFDE277d4306a2da7Bd762", - "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000006119e37bd66406a1db74920ac79c15fb8411ba76", + "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", + "constructorArguments": "", "isProxy": false, - "name": "FallbackRoutingHook" + "name": "InterchainGasPaymaster" }, { - "address": "0x4Eb82Ee35b0a1c1d776E3a3B547f9A9bA6FCC9f2", - "constructorArguments": "", - "isProxy": false, - "name": "PausableHook" - } - ], - "viction": [ + "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", + "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, { - "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", "constructorArguments": "", "isProxy": false, - "name": "ProxyAdmin" + "name": "StorageGasOracle" }, { - "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", - "constructorArguments": "0000000000000000000000000000000000000000000000000000000000000058", + "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", + "constructorArguments": "", "isProxy": false, - "name": "Mailbox" + "name": "InterchainGasPaymaster" }, { - "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", - "constructorArguments": "0000000000000000000000004ed7d626f1e96cd1c0401607bf70d95243e3ded10000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", + "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0x149db7afD694722747035d5AEC7007ccb6F8f112", - "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", - "isProxy": false, - "name": "MerkleTreeHook" - }, - { - "address": "0x19dc38aeae620380430C200a6E990D5Af5480117", + "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", "constructorArguments": "", "isProxy": false, "name": "StorageGasOracle" }, { - "address": "0xBF12ef4B9f307463D3FB59c3604F294dDCe287E2", + "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", "constructorArguments": "", "isProxy": false, "name": "InterchainGasPaymaster" }, { - "address": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", - "constructorArguments": "000000000000000000000000bf12ef4b9f307463d3fb59c3604f294ddce287e20000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", + "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0x149db7afD694722747035d5AEC7007ccb6F8f112", - "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", - "isProxy": false, - "name": "MerkleTreeHook" - }, - { - "address": "0x19dc38aeae620380430C200a6E990D5Af5480117", + "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", "constructorArguments": "", "isProxy": false, "name": "StorageGasOracle" }, { - "address": "0xBF12ef4B9f307463D3FB59c3604F294dDCe287E2", + "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", "constructorArguments": "", "isProxy": false, "name": "InterchainGasPaymaster" }, { - "address": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", - "constructorArguments": "000000000000000000000000bf12ef4b9f307463d3fb59c3604f294ddce287e20000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", + "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0xd83A4F747fE80Ed98839e05079B1B7Fe037b1638", - "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", + "constructorArguments": "", "isProxy": false, - "name": "ProtocolFee" + "name": "StorageGasOracle" }, { - "address": "0x2fa5F5C96419C222cDbCeC797D696e6cE428A7A9", - "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", + "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", + "constructorArguments": "", "isProxy": false, - "name": "ValidatorAnnounce" + "name": "InterchainGasPaymaster" }, { - "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", + "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", "constructorArguments": "", "isProxy": false, - "name": "ProxyAdmin" + "name": "StorageGasOracle" }, { - "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", - "constructorArguments": "0000000000000000000000000000000000000000000000000000000000000058", + "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", + "constructorArguments": "", "isProxy": false, - "name": "Mailbox" + "name": "InterchainGasPaymaster" }, { - "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", - "constructorArguments": "0000000000000000000000004ed7d626f1e96cd1c0401607bf70d95243e3ded10000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", + "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0x149db7afD694722747035d5AEC7007ccb6F8f112", - "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", - "isProxy": false, - "name": "MerkleTreeHook" - }, - { - "address": "0x19dc38aeae620380430C200a6E990D5Af5480117", + "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", "constructorArguments": "", "isProxy": false, "name": "StorageGasOracle" }, { - "address": "0xBF12ef4B9f307463D3FB59c3604F294dDCe287E2", + "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", "constructorArguments": "", "isProxy": false, "name": "InterchainGasPaymaster" }, { - "address": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", - "constructorArguments": "000000000000000000000000bf12ef4b9f307463d3fb59c3604f294ddce287e20000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", + "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0x149db7afD694722747035d5AEC7007ccb6F8f112", - "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", - "isProxy": false, - "name": "MerkleTreeHook" - }, - { - "address": "0x19dc38aeae620380430C200a6E990D5Af5480117", + "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", "constructorArguments": "", "isProxy": false, "name": "StorageGasOracle" }, { - "address": "0xBF12ef4B9f307463D3FB59c3604F294dDCe287E2", + "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", "constructorArguments": "", "isProxy": false, "name": "InterchainGasPaymaster" }, { - "address": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", - "constructorArguments": "000000000000000000000000bf12ef4b9f307463d3fb59c3604f294ddce287e20000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", + "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0xd83A4F747fE80Ed98839e05079B1B7Fe037b1638", - "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", + "constructorArguments": "", "isProxy": false, - "name": "ProtocolFee" + "name": "StorageGasOracle" }, { - "address": "0x2fa5F5C96419C222cDbCeC797D696e6cE428A7A9", - "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", + "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", + "constructorArguments": "", "isProxy": false, - "name": "ValidatorAnnounce" + "name": "InterchainGasPaymaster" }, { - "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", + "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", "constructorArguments": "", "isProxy": false, - "name": "ProxyAdmin" + "name": "StorageGasOracle" }, { - "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", - "constructorArguments": "0000000000000000000000000000000000000000000000000000000000000058", + "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", + "constructorArguments": "", "isProxy": false, - "name": "Mailbox" + "name": "InterchainGasPaymaster" }, { - "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", - "constructorArguments": "0000000000000000000000004ed7d626f1e96cd1c0401607bf70d95243e3ded10000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", + "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0x149db7afD694722747035d5AEC7007ccb6F8f112", - "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", - "isProxy": false, - "name": "MerkleTreeHook" - }, - { - "address": "0x19dc38aeae620380430C200a6E990D5Af5480117", + "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", "constructorArguments": "", "isProxy": false, "name": "StorageGasOracle" }, { - "address": "0xBF12ef4B9f307463D3FB59c3604F294dDCe287E2", + "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", "constructorArguments": "", "isProxy": false, "name": "InterchainGasPaymaster" }, { - "address": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", - "constructorArguments": "000000000000000000000000bf12ef4b9f307463d3fb59c3604f294ddce287e20000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", + "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0x149db7afD694722747035d5AEC7007ccb6F8f112", - "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", - "isProxy": false, - "name": "MerkleTreeHook" - }, - { - "address": "0x19dc38aeae620380430C200a6E990D5Af5480117", + "address": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", "constructorArguments": "", "isProxy": false, "name": "StorageGasOracle" }, { - "address": "0xBF12ef4B9f307463D3FB59c3604F294dDCe287E2", + "address": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", "constructorArguments": "", "isProxy": false, "name": "InterchainGasPaymaster" }, { - "address": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", - "constructorArguments": "000000000000000000000000bf12ef4b9f307463d3fb59c3604f294ddce287e20000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", + "constructorArguments": "000000000000000000000000e0c452dda7506f0f4de5c8c1d383f7ad866ea4f00000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" }, { - "address": "0xd83A4F747fE80Ed98839e05079B1B7Fe037b1638", + "address": "0xea820f9BCFD5E16a0dd42071EB61A29874Ad81A4", "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", "isProxy": false, "name": "ProtocolFee" }, { - "address": "0x2fa5F5C96419C222cDbCeC797D696e6cE428A7A9", + "address": "0x48083C69f5a42c6B69ABbAd48AE195BD36770ee2", "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", "isProxy": false, "name": "ValidatorAnnounce" diff --git a/typescript/infra/config/environments/mainnet3/funding.ts b/typescript/infra/config/environments/mainnet3/funding.ts index 64cf0fd66..f9f15f0a8 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.js'; export const keyFunderConfig: KeyFunderConfig = { docker: { repo: 'gcr.io/abacus-labs-dev/hyperlane-monorepo', - tag: '5d1391c-20240418-100607', + tag: 'b22a0f4-20240523-140812', }, // We're currently using the same deployer/key funder key as mainnet2. // To minimize nonce clobbering we offset the key funder cron @@ -17,7 +17,7 @@ export const keyFunderConfig: KeyFunderConfig = { cronSchedule: '45 * * * *', // Every hour at the 45-minute mark namespace: environment, prometheusPushGateway: - 'http://prometheus-pushgateway.monitoring.svc.cluster.local:9091', + 'http://prometheus-prometheus-pushgateway.monitoring.svc.cluster.local:9091', contextFundingFrom: Contexts.Hyperlane, contextsAndRolesToFund: { [Contexts.Hyperlane]: [Role.Relayer, Role.Kathy], @@ -26,39 +26,47 @@ export const keyFunderConfig: KeyFunderConfig = { connectionType: RpcConsensusType.Fallback, // desired balance config desiredBalancePerChain: { + arbitrum: '0.5', + ancient8: '0.5', avalanche: '5', - bsc: '5', + base: '0.5', blast: '0.2', + bsc: '5', celo: '3', ethereum: '0.5', gnosis: '5', inevm: '3', + mantapacific: '0.2', mode: '0.2', moonbeam: '5', - polygon: '20', - viction: '3', - // Funder boosts itself upto 5x balance on L2 before dispersing funds - arbitrum: '0.5', - base: '0.5', optimism: '0.5', + polygon: '20', polygonzkevm: '0.5', + redstone: '0.2', scroll: '0.5', - ancient8: '0.5', + viction: '3', + zetachain: '20', }, desiredKathyBalancePerChain: { arbitrum: '0.1', + ancient8: '0', avalanche: '6', base: '0.05', + blast: '0', bsc: '0.35', celo: '150', ethereum: '0.4', gnosis: '100', inevm: '0.05', + mantapacific: '0', + mode: '0', moonbeam: '250', optimism: '0.1', polygon: '85', polygonzkevm: '0.05', + redstone: '0', scroll: '0.05', viction: '0.05', + zetachain: '0', }, }; diff --git a/typescript/infra/config/environments/mainnet3/gasPrices.json b/typescript/infra/config/environments/mainnet3/gasPrices.json index 7d34ef96f..8fd3ca2aa 100644 --- a/typescript/infra/config/environments/mainnet3/gasPrices.json +++ b/typescript/infra/config/environments/mainnet3/gasPrices.json @@ -28,7 +28,7 @@ "decimals": 9 }, "ethereum": { - "amount": "26.346912847", + "amount": "20", "decimals": 9 }, "mantapacific": { @@ -63,6 +63,10 @@ "amount": "3.95", "decimals": 9 }, + "redstone": { + "amount": "0.0003", + "decimals": 9 + }, "inevm": { "amount": "0.1", "decimals": 9 @@ -78,5 +82,9 @@ "injective": { "amount": "700000000", "decimals": 1 + }, + "zetachain": { + "amount": "0.0001", + "decimals": 9 } } diff --git a/typescript/infra/config/environments/mainnet3/helloworld.ts b/typescript/infra/config/environments/mainnet3/helloworld.ts index c4d1920ca..4f15ba912 100644 --- a/typescript/infra/config/environments/mainnet3/helloworld.ts +++ b/typescript/infra/config/environments/mainnet3/helloworld.ts @@ -15,7 +15,7 @@ export const hyperlane: HelloWorldConfig = { kathy: { docker: { repo: 'gcr.io/abacus-labs-dev/hyperlane-monorepo', - tag: '86b7f98-20231207-153806', + tag: 'b22a0f4-20240523-140812', }, chainsToSkip: [], runEnv: environment, @@ -36,7 +36,7 @@ export const releaseCandidate: HelloWorldConfig = { kathy: { docker: { repo: 'gcr.io/abacus-labs-dev/hyperlane-monorepo', - tag: '0e3f73f-20240206-160718', + tag: 'b22a0f4-20240523-140812', }, chainsToSkip: [], runEnv: environment, diff --git a/typescript/infra/config/environments/mainnet3/infrastructure.ts b/typescript/infra/config/environments/mainnet3/infrastructure.ts index 1ffdf19fc..36c15751c 100644 --- a/typescript/infra/config/environments/mainnet3/infrastructure.ts +++ b/typescript/infra/config/environments/mainnet3/infrastructure.ts @@ -9,7 +9,7 @@ export const infrastructure: InfrastructureConfig = { prometheus: { deployName: 'prometheus', // Node exporter does not work with GKE Autopilot - nodeExporterEnabled: false, + nodeExporterEnabled: true, helmChart: { // See https://github.com/prometheus-community/helm-charts#usage repository: { @@ -17,7 +17,7 @@ export const infrastructure: InfrastructureConfig = { url: 'https://prometheus-community.github.io/helm-charts', }, name: 'prometheus', - version: '15.0.1', + version: '25.21.0', }, }, }, diff --git a/typescript/infra/config/environments/mainnet3/ism/verification.json b/typescript/infra/config/environments/mainnet3/ism/verification.json index fbbaaf72d..cd76f9f2b 100644 --- a/typescript/infra/config/environments/mainnet3/ism/verification.json +++ b/typescript/infra/config/environments/mainnet3/ism/verification.json @@ -3043,6 +3043,68 @@ "name": "DomaingRoutingIsm" } ], + "redstone": [ + { + "address": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", + "constructorArguments": "", + "isProxy": false, + "name": "StaticMerkleRootMultisigIsmFactory" + }, + { + "address": "0xAF03386044373E2fe26C5b1dCedF5a7e854a7a3F", + "constructorArguments": "", + "isProxy": true, + "name": "StaticMerkleRootMultisigIsm" + }, + { + "address": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", + "constructorArguments": "", + "isProxy": false, + "name": "StaticMessageIdMultisigIsmFactory" + }, + { + "address": "0x882CD0C5D50b6dD74b36Da4BDb059507fddEDdf2", + "constructorArguments": "", + "isProxy": true, + "name": "StaticMessageIdMultisigIsm" + }, + { + "address": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", + "constructorArguments": "", + "isProxy": false, + "name": "StaticAggregationIsmFactory" + }, + { + "address": "0x19930232E9aFC4f4F09d09fe2375680fAc2100D0", + "constructorArguments": "", + "isProxy": true, + "name": "StaticAggregationIsm" + }, + { + "address": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", + "constructorArguments": "", + "isProxy": false, + "name": "StaticAggregationHookFactory" + }, + { + "address": "0x12Ed1BbA182CbC63692F813651BD493B7445C874", + "constructorArguments": "", + "isProxy": true, + "name": "StaticAggregationHook" + }, + { + "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "constructorArguments": "", + "isProxy": false, + "name": "DomainRoutingIsmFactory" + }, + { + "address": "0x3b9f24fD2ecfed0d3A88fa7f0E4e5747671981D7", + "constructorArguments": "", + "isProxy": true, + "name": "DomaingRoutingIsm" + } + ], "scroll": [ { "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", @@ -3352,5 +3414,67 @@ "isProxy": true, "name": "DomaingRoutingIsm" } + ], + "zetachain": [ + { + "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", + "constructorArguments": "", + "isProxy": false, + "name": "StaticMerkleRootMultisigIsmFactory" + }, + { + "address": "0x4725F7b8037513915aAf6D6CBDE2920E28540dDc", + "constructorArguments": "", + "isProxy": true, + "name": "StaticMerkleRootMultisigIsm" + }, + { + "address": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", + "constructorArguments": "", + "isProxy": false, + "name": "StaticMessageIdMultisigIsmFactory" + }, + { + "address": "0xAF03386044373E2fe26C5b1dCedF5a7e854a7a3F", + "constructorArguments": "", + "isProxy": true, + "name": "StaticMessageIdMultisigIsm" + }, + { + "address": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", + "constructorArguments": "", + "isProxy": false, + "name": "StaticAggregationIsmFactory" + }, + { + "address": "0x882CD0C5D50b6dD74b36Da4BDb059507fddEDdf2", + "constructorArguments": "", + "isProxy": true, + "name": "StaticAggregationIsm" + }, + { + "address": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", + "constructorArguments": "", + "isProxy": false, + "name": "StaticAggregationHookFactory" + }, + { + "address": "0x19930232E9aFC4f4F09d09fe2375680fAc2100D0", + "constructorArguments": "", + "isProxy": true, + "name": "StaticAggregationHook" + }, + { + "address": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", + "constructorArguments": "", + "isProxy": false, + "name": "DomainRoutingIsmFactory" + }, + { + "address": "0x12Ed1BbA182CbC63692F813651BD493B7445C874", + "constructorArguments": "", + "isProxy": true, + "name": "DomaingRoutingIsm" + } ] } diff --git a/typescript/infra/config/environments/mainnet3/liquidityLayer.ts b/typescript/infra/config/environments/mainnet3/liquidityLayer.ts index e4aec1e77..ac311f447 100644 --- a/typescript/infra/config/environments/mainnet3/liquidityLayer.ts +++ b/typescript/infra/config/environments/mainnet3/liquidityLayer.ts @@ -49,6 +49,6 @@ export const relayerConfig: LiquidityLayerRelayerConfig = { }, namespace: environment, prometheusPushGateway: - 'http://prometheus-pushgateway.monitoring.svc.cluster.local:9091', + 'http://prometheus-prometheus-pushgateway.monitoring.svc.cluster.local:9091', connectionType: RpcConsensusType.Single, }; diff --git a/typescript/infra/config/environments/mainnet3/owners.ts b/typescript/infra/config/environments/mainnet3/owners.ts index 3b2d89d7a..bf1239efc 100644 --- a/typescript/infra/config/environments/mainnet3/owners.ts +++ b/typescript/infra/config/environments/mainnet3/owners.ts @@ -55,6 +55,7 @@ export const owners: ChainMap = Object.fromEntries( proxyAdmin: timelocks[local] ?? safes[local] ?? DEPLOYER, validatorAnnounce: DEPLOYER, // unused testRecipient: DEPLOYER, + fallbackRoutingHook: DEPLOYER, }, }, ]), diff --git a/typescript/infra/config/environments/mainnet3/supportedChainNames.ts b/typescript/infra/config/environments/mainnet3/supportedChainNames.ts index 03a7d1655..5a0042c8e 100644 --- a/typescript/infra/config/environments/mainnet3/supportedChainNames.ts +++ b/typescript/infra/config/environments/mainnet3/supportedChainNames.ts @@ -4,21 +4,23 @@ export const supportedChainNames = [ 'arbitrum', 'ancient8', 'avalanche', + 'base', 'blast', 'bsc', 'celo', 'ethereum', - 'neutron', + 'gnosis', + 'inevm', + 'injective', 'mantapacific', 'mode', 'moonbeam', + 'neutron', 'optimism', 'polygon', - 'gnosis', - 'base', - 'scroll', 'polygonzkevm', - 'injective', - 'inevm', + 'redstone', + 'scroll', 'viction', + 'zetachain', ]; diff --git a/typescript/infra/config/environments/mainnet3/tokenPrices.json b/typescript/infra/config/environments/mainnet3/tokenPrices.json index eeb0b6bcf..dac9798f6 100644 --- a/typescript/infra/config/environments/mainnet3/tokenPrices.json +++ b/typescript/infra/config/environments/mainnet3/tokenPrices.json @@ -1,22 +1,24 @@ { - "arbitrum": "3174.87", - "ancient8": "3174.87", - "avalanche": "38.39", - "base": "3174.87", - "blast": "3174.87", - "bsc": "609.32", - "celo": "0.860923", - "ethereum": "3174.87", - "mantapacific": "3174.87", - "mode": "3174.87", - "moonbeam": "0.338118", - "optimism": "3174.87", - "polygon": "0.730041", - "gnosis": "0.993981", - "scroll": "3174.87", - "polygonzkevm": "3174.87", - "inevm": "28.12", - "viction": "0.775722", - "neutron": "0.842639", - "injective": "28.12" + "arbitrum": "2919.87", + "ancient8": "2919.87", + "avalanche": "33.19", + "base": "2919.87", + "blast": "2919.87", + "bsc": "570.1", + "celo": "0.738559", + "ethereum": "2919.87", + "gnosis": "1.005", + "inevm": "21.59", + "mantapacific": "2919.87", + "mode": "2919.87", + "moonbeam": "0.253144", + "optimism": "2919.87", + "polygon": "0.663051", + "polygonzkevm": "2919.87", + "redstone": "2919.87", + "scroll": "2919.87", + "viction": "0.424231", + "zetachain": "1.53", + "injective": "21.59", + "neutron": "0.606906" } diff --git a/typescript/infra/config/environments/mainnet3/validators.ts b/typescript/infra/config/environments/mainnet3/validators.ts index 45d7a469a..444bc70f2 100644 --- a/typescript/infra/config/environments/mainnet3/validators.ts +++ b/typescript/infra/config/environments/mainnet3/validators.ts @@ -380,5 +380,33 @@ export const validatorChainConfig = ( 'mode', ), }, + redstone: { + interval: 5, + reorgPeriod: getReorgPeriod('redstone'), + validators: validatorsConfig( + { + [Contexts.Hyperlane]: ['0x1400b9737007f7978d8b4bbafb4a69c83f0641a7'], + [Contexts.ReleaseCandidate]: [ + '0x51ed7127c0afc0513a0f141e910c5e02b2a9a4b5', + ], + [Contexts.Neutron]: [], + }, + 'redstone', + ), + }, + zetachain: { + interval: 5, + reorgPeriod: getReorgPeriod('zetachain'), + validators: validatorsConfig( + { + [Contexts.Hyperlane]: ['0xa3bca0b80317dbf9c7dce16a16ac89f4ff2b23ef'], + [Contexts.ReleaseCandidate]: [ + '0xa13d146b47242671466e4041f5fe68d22a2ffe09', + ], + [Contexts.Neutron]: [], + }, + 'zetachain', + ), + }, }; }; diff --git a/typescript/infra/config/environments/testnet4/funding.ts b/typescript/infra/config/environments/testnet4/funding.ts index 56dbd116e..42d9c75c9 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.js'; export const keyFunderConfig: KeyFunderConfig = { docker: { repo: 'gcr.io/abacus-labs-dev/hyperlane-monorepo', - tag: 'b0811ba-20240411-151216', + tag: 'b22a0f4-20240523-140812', }, // We're currently using the same deployer key as testnet2. // To minimize nonce clobbering we offset the key funder cron @@ -17,7 +17,7 @@ export const keyFunderConfig: KeyFunderConfig = { cronSchedule: '15 * * * *', // Every hour at the 15-minute mark namespace: environment, prometheusPushGateway: - 'http://prometheus-pushgateway.monitoring.svc.cluster.local:9091', + 'http://prometheus-prometheus-pushgateway.monitoring.svc.cluster.local:9091', contextFundingFrom: Contexts.Hyperlane, contextsAndRolesToFund: { [Contexts.Hyperlane]: [Role.Relayer, Role.Kathy], @@ -30,11 +30,15 @@ export const keyFunderConfig: KeyFunderConfig = { bsctestnet: '5', fuji: '5', plumetestnet: '0.2', - sepolia: '5', - // Funder boosts itself upto 5x balance on L2 before dispersing funds scrollsepolia: '1', + sepolia: '5', }, desiredKathyBalancePerChain: { + alfajores: '1', + bsctestnet: '1', + fuji: '1', plumetestnet: '0.05', + scrollsepolia: '1', + sepolia: '1', }, }; diff --git a/typescript/infra/config/environments/testnet4/helloworld.ts b/typescript/infra/config/environments/testnet4/helloworld.ts index 96903af85..e6f094c7d 100644 --- a/typescript/infra/config/environments/testnet4/helloworld.ts +++ b/typescript/infra/config/environments/testnet4/helloworld.ts @@ -15,7 +15,7 @@ export const hyperlaneHelloworld: HelloWorldConfig = { kathy: { docker: { repo: 'gcr.io/abacus-labs-dev/hyperlane-monorepo', - tag: '17ac515-20240402-171932', + tag: 'b22a0f4-20240523-140812', }, chainsToSkip: [], runEnv: environment, @@ -35,7 +35,7 @@ export const releaseCandidateHelloworld: HelloWorldConfig = { kathy: { docker: { repo: 'gcr.io/abacus-labs-dev/hyperlane-monorepo', - tag: '17ac515-20240402-171932', + tag: 'b22a0f4-20240523-140812', }, chainsToSkip: [], runEnv: environment, diff --git a/typescript/infra/config/environments/testnet4/infrastructure.ts b/typescript/infra/config/environments/testnet4/infrastructure.ts index 33d26d2fe..eb747804c 100644 --- a/typescript/infra/config/environments/testnet4/infrastructure.ts +++ b/typescript/infra/config/environments/testnet4/infrastructure.ts @@ -9,7 +9,7 @@ export const infrastructure: InfrastructureConfig = { prometheus: { deployName: 'prometheus', // Node exporter does not work with GKE Autopilot - nodeExporterEnabled: false, + nodeExporterEnabled: true, helmChart: { // See https://github.com/prometheus-community/helm-charts#usage repository: { @@ -17,7 +17,7 @@ export const infrastructure: InfrastructureConfig = { url: 'https://prometheus-community.github.io/helm-charts', }, name: 'prometheus', - version: '15.0.1', + version: '25.21.0', }, }, }, diff --git a/typescript/infra/config/environments/testnet4/middleware.ts b/typescript/infra/config/environments/testnet4/middleware.ts index e68cee52f..607b4a3f6 100644 --- a/typescript/infra/config/environments/testnet4/middleware.ts +++ b/typescript/infra/config/environments/testnet4/middleware.ts @@ -11,6 +11,6 @@ export const liquidityLayerRelayerConfig: LiquidityLayerRelayerConfig = { }, namespace: environment, prometheusPushGateway: - 'http://prometheus-pushgateway.monitoring.svc.cluster.local:9091', + 'http://prometheus-prometheus-pushgateway.monitoring.svc.cluster.local:9091', connectionType: RpcConsensusType.Single, }; diff --git a/typescript/infra/config/registry.ts b/typescript/infra/config/registry.ts index 91abea01c..a85488014 100644 --- a/typescript/infra/config/registry.ts +++ b/typescript/infra/config/registry.ts @@ -2,7 +2,7 @@ import { dirname, join } from 'path'; import { fileURLToPath } from 'url'; import { ChainAddresses } from '@hyperlane-xyz/registry'; -import { LocalRegistry } from '@hyperlane-xyz/registry/local'; +import { FileSystemRegistry } from '@hyperlane-xyz/registry/fs'; import { ChainMap, ChainMetadata, @@ -10,7 +10,7 @@ import { getDomainId as resolveDomainId, getReorgPeriod as resolveReorgPeriod, } from '@hyperlane-xyz/sdk'; -import { objFilter, rootLogger } from '@hyperlane-xyz/utils'; +import { assert, objFilter, rootLogger } from '@hyperlane-xyz/utils'; import type { DeployEnvironment } from '../src/config/environment.js'; @@ -29,17 +29,17 @@ const DEFAULT_REGISTRY_URI = join( // A global Registry singleton // All uses of chain metadata or chain address artifacts should go through this registry. -let registry: LocalRegistry; +let registry: FileSystemRegistry; -export function setRegistry(reg: LocalRegistry) { +export function setRegistry(reg: FileSystemRegistry) { registry = reg; } -export function getRegistry(): LocalRegistry { +export function getRegistry(): FileSystemRegistry { if (!registry) { const registryUri = process.env.REGISTRY_URI || DEFAULT_REGISTRY_URI; rootLogger.info('Using registry URI:', registryUri); - registry = new LocalRegistry({ + registry = new FileSystemRegistry({ uri: registryUri, logger: rootLogger.child({ module: 'infra-registry' }), }); @@ -55,15 +55,19 @@ export function getChain(chainName: ChainName): ChainMetadata { if (testChains.includes(chainName)) { return testChainMetadata[chainName]; } - return getRegistry().getChainMetadata(chainName); + const chain = getRegistry().getChainMetadata(chainName); + assert(chain, `Chain not found: ${chainName}`); + return chain; } export function getDomainId(chainName: ChainName): number { - return resolveDomainId(getChain(chainName)); + const chain = getChain(chainName); + return resolveDomainId(chain); } export function getReorgPeriod(chainName: ChainName): number { - return resolveReorgPeriod(getChain(chainName)); + const chain = getChain(chainName); + return resolveReorgPeriod(chain); } export function getChainMetadata(): ChainMap { diff --git a/typescript/infra/config/warp.ts b/typescript/infra/config/warp.ts index a878cfc24..43fe0beda 100644 --- a/typescript/infra/config/warp.ts +++ b/typescript/infra/config/warp.ts @@ -4,8 +4,7 @@ import { ChainMap, HyperlaneIsmFactory, MultiProvider, - RouterConfig, - TokenConfig, + TokenRouterConfig, TokenType, buildAggregationIsmConfigs, defaultMultisigConfigs, @@ -21,7 +20,7 @@ import { DEPLOYER } from './environments/mainnet3/owners.js'; export async function getWarpConfig( multiProvider: MultiProvider, envConfig: EnvironmentConfig, -): Promise> { +): Promise> { const { core } = await getHyperlaneCore(envConfig.environment, multiProvider); const ismFactory = HyperlaneIsmFactory.fromAddressesMap( getAddresses(envConfig.environment, Modules.PROXY_FACTORY), @@ -43,7 +42,7 @@ export async function getWarpConfig( const routerConfig = core.getRouterConfig(envConfig.owners); - const ethereum: TokenConfig & RouterConfig = { + const ethereum: TokenRouterConfig = { ...routerConfig.ethereum, type: TokenType.collateral, token: tokens.ethereum.USDC, @@ -59,7 +58,7 @@ export async function getWarpConfig( // TokenMetadata, but in practice these are actually inferred from a // collateral config. To avoid needing to specify the TokenMetadata, just // ts-ignore for synthetic tokens. - const ancient8: TokenConfig & RouterConfig = { + const ancient8: TokenRouterConfig = { ...routerConfig.ancient8, type: TokenType.synthetic, // Uses the default ISM diff --git a/typescript/infra/package.json b/typescript/infra/package.json index 428569c33..33f8d6194 100644 --- a/typescript/infra/package.json +++ b/typescript/infra/package.json @@ -1,7 +1,7 @@ { "name": "@hyperlane-xyz/infra", "description": "Infrastructure utilities for the Hyperlane Network", - "version": "3.12.2", + "version": "3.13.0", "dependencies": { "@arbitrum/sdk": "^3.0.0", "@aws-sdk/client-iam": "^3.74.0", @@ -12,10 +12,10 @@ "@ethersproject/experimental": "^5.7.0", "@ethersproject/hardware-wallets": "^5.7.0", "@ethersproject/providers": "^5.7.2", - "@hyperlane-xyz/helloworld": "3.12.2", - "@hyperlane-xyz/registry": "^1.0.7", - "@hyperlane-xyz/sdk": "3.12.2", - "@hyperlane-xyz/utils": "3.12.2", + "@hyperlane-xyz/helloworld": "3.13.0", + "@hyperlane-xyz/registry": "1.3.0", + "@hyperlane-xyz/sdk": "3.13.0", + "@hyperlane-xyz/utils": "3.13.0", "@nomiclabs/hardhat-etherscan": "^3.0.3", "@solana/web3.js": "^1.78.0", "asn1.js": "5.4.1", diff --git a/typescript/infra/scripts/agent-utils.ts b/typescript/infra/scripts/agent-utils.ts index 7226bdf0b..d027d5aad 100644 --- a/typescript/infra/scripts/agent-utils.ts +++ b/typescript/infra/scripts/agent-utils.ts @@ -164,6 +164,13 @@ export function withBuildArtifactPath(args: Argv) { .alias('b', 'buildArtifactPath'); } +export function withConcurrentDeploy(args: Argv) { + return args + .describe('concurrentDeploy', 'If enabled, runs all deploys concurrently') + .boolean('concurrentDeploy') + .default('concurrentDeploy', false); +} + // not requiring to build coreConfig to get agentConfig export async function getAgentConfigsBasedOnArgs(argv?: { environment: DeployEnvironment; diff --git a/typescript/infra/scripts/announce-validators.ts b/typescript/infra/scripts/announce-validators.ts index 05250bad8..09748de98 100644 --- a/typescript/infra/scripts/announce-validators.ts +++ b/typescript/infra/scripts/announce-validators.ts @@ -6,7 +6,7 @@ import * as path from 'path'; import { ChainName } from '@hyperlane-xyz/sdk'; import { getChains } from '../config/registry.js'; -import { S3Validator } from '../src/agents/aws/validator.js'; +import { InfraS3Validator } from '../src/agents/aws/validator.js'; import { CheckpointSyncerType } from '../src/config/agent/validator.js'; import { isEthereumProtocolChain } from '../src/utils/utils.js'; @@ -47,7 +47,7 @@ async function main() { chains.push(chain!); if (location.startsWith('s3://')) { - const validator = await S3Validator.fromStorageLocation(location); + const validator = await InfraS3Validator.fromStorageLocation(location); announcements.push({ storageLocation: validator.storageLocation(), announcement: await validator.getAnnouncement(), @@ -87,13 +87,13 @@ async function main() { ) { const contracts = core.getContracts(validatorChain); const localDomain = multiProvider.getDomainId(validatorChain); - const validator = new S3Validator( - validatorBaseConfig.address, - localDomain, - contracts.mailbox.address, - validatorBaseConfig.checkpointSyncer.bucket, - validatorBaseConfig.checkpointSyncer.region, - undefined, + const validator = new InfraS3Validator( + { + localDomain, + address: validatorBaseConfig.address, + mailbox: contracts.mailbox.address, + }, + validatorBaseConfig.checkpointSyncer, ); announcements.push({ storageLocation: validator.storageLocation(), diff --git a/typescript/infra/scripts/deploy.ts b/typescript/infra/scripts/deploy.ts index c761c59c1..733bfd819 100644 --- a/typescript/infra/scripts/deploy.ts +++ b/typescript/infra/scripts/deploy.ts @@ -42,6 +42,7 @@ import { getArgs, getModuleDirectory, withBuildArtifactPath, + withConcurrentDeploy, withContext, withModuleAndFork, withNetwork, @@ -56,8 +57,11 @@ async function main() { environment, network, buildArtifactPath, + concurrentDeploy, } = await withContext( - withNetwork(withModuleAndFork(withBuildArtifactPath(getArgs()))), + withConcurrentDeploy( + withNetwork(withModuleAndFork(withBuildArtifactPath(getArgs()))), + ), ).argv; const envConfig = getEnvironmentConfig(environment); @@ -113,6 +117,7 @@ async function main() { multiProvider, ismFactory, contractVerifier, + concurrentDeploy, ); } else if (module === Modules.WARP) { const ismFactory = HyperlaneIsmFactory.fromAddressesMap( diff --git a/typescript/infra/scripts/funding/fund-keys-from-deployer.ts b/typescript/infra/scripts/funding/fund-keys-from-deployer.ts index 5ee9aba1c..02b15a978 100644 --- a/typescript/infra/scripts/funding/fund-keys-from-deployer.ts +++ b/typescript/infra/scripts/funding/fund-keys-from-deployer.ts @@ -575,10 +575,18 @@ class ContextFunder { } private getDesiredBalanceForRole(chain: ChainName, role: Role): BigNumber { - const desiredBalanceEther = - role === Role.Kathy && this.desiredKathyBalancePerChain[chain] - ? this.desiredKathyBalancePerChain[chain] - : this.desiredBalancePerChain[chain]; + let desiredBalanceEther: string | undefined; + if (role === Role.Kathy) { + const desiredKathyBalance = this.desiredKathyBalancePerChain[chain]; + if (desiredKathyBalance === undefined) { + logger.warn({ chain }, 'No desired balance for Kathy, not funding'); + desiredBalanceEther = '0'; + } else { + desiredBalanceEther = this.desiredKathyBalancePerChain[chain]; + } + } else { + desiredBalanceEther = this.desiredBalancePerChain[chain]; + } let desiredBalance = ethers.utils.parseEther(desiredBalanceEther ?? '0'); if (this.context === Contexts.ReleaseCandidate) { desiredBalance = desiredBalance diff --git a/typescript/infra/scripts/list-validator-checkpoint-indices.ts b/typescript/infra/scripts/list-validator-checkpoint-indices.ts index ca539be79..f3cfc6c1c 100644 --- a/typescript/infra/scripts/list-validator-checkpoint-indices.ts +++ b/typescript/infra/scripts/list-validator-checkpoint-indices.ts @@ -1,6 +1,6 @@ import { concurrentMap } from '@hyperlane-xyz/utils'; -import { S3Validator } from '../src/agents/aws/validator.js'; +import { InfraS3Validator } from '../src/agents/aws/validator.js'; import { getArgs, getValidatorsByChain } from './agent-utils.js'; import { getEnvironmentConfig, getHyperlaneCore } from './core-utils.js'; @@ -26,7 +26,7 @@ async function main() { let identifier = validator; if (storageLocations.length == 1 && storageLocations[0].length == 1) { try { - const s3Validator = await S3Validator.fromStorageLocation( + const s3Validator = await InfraS3Validator.fromStorageLocation( storageLocations[0][0], ); identifier = storageLocations[0][0]; diff --git a/typescript/infra/scripts/verify-validators.ts b/typescript/infra/scripts/verify-validators.ts index 755d568a9..561ef2a5c 100644 --- a/typescript/infra/scripts/verify-validators.ts +++ b/typescript/infra/scripts/verify-validators.ts @@ -1,6 +1,6 @@ import { objMap, promiseObjAll } from '@hyperlane-xyz/utils'; -import { S3Validator } from '../src/agents/aws/validator.js'; +import { InfraS3Validator } from '../src/agents/aws/validator.js'; import { getArgs, getValidatorsByChain } from './agent-utils.js'; import { getEnvironmentConfig, getHyperlaneCore } from './core-utils.js'; @@ -21,20 +21,20 @@ async function main() { if (storageLocations[i].length != 1) { throw new Error('Only support single announcement'); } - return S3Validator.fromStorageLocation(storageLocations[i][0]); + return InfraS3Validator.fromStorageLocation(storageLocations[i][0]); }), ); const controlValidator = validators[0]; await Promise.all( validators.slice(1).map(async (prospectiveValidator) => { const address = prospectiveValidator.address; - const bucket = prospectiveValidator.s3Bucket.bucket; + const bucket = prospectiveValidator.s3Bucket; try { const metrics = await prospectiveValidator.compare( controlValidator, ); console.log( - `${chain} ${bucket} validators against control ${controlValidator.s3Bucket.bucket}`, + `${chain} ${bucket} validators against control ${controlValidator.s3Bucket}`, ); console.table(metrics); } catch (error) { diff --git a/typescript/infra/src/agents/aws/validator.ts b/typescript/infra/src/agents/aws/validator.ts index 2c6b48c60..398190a9c 100644 --- a/typescript/infra/src/agents/aws/validator.ts +++ b/typescript/infra/src/agents/aws/validator.ts @@ -1,5 +1,5 @@ +import { S3Receipt, S3Validator } from '@hyperlane-xyz/sdk'; import { - BaseValidator, Checkpoint, HexString, S3Checkpoint, @@ -8,8 +8,6 @@ import { isS3CheckpointWithId, } from '@hyperlane-xyz/utils'; -import { S3Receipt, S3Wrapper } from './s3.js'; - export enum CheckpointStatus { EXTRA = '➕', MISSING = '❓', @@ -35,71 +33,22 @@ type S3CheckpointReceipt = S3Receipt; const checkpointWithMessageIdKey = (checkpointIndex: number) => `checkpoint_${checkpointIndex}_with_id.json`; const LATEST_KEY = 'checkpoint_latest_index.json'; -const ANNOUNCEMENT_KEY = 'announcement.json'; -const LOCATION_PREFIX = 's3://'; /** * Extension of BaseValidator that includes AWS S3 utilities. */ -export class S3Validator extends BaseValidator { - s3Bucket: S3Wrapper; - - constructor( - address: string, - localDomain: number, - mailbox: string, - s3Bucket: string, - s3Region: string, - s3Folder: string | undefined, - ) { - super(address, localDomain, mailbox); - this.s3Bucket = new S3Wrapper(s3Bucket, s3Region, s3Folder); - } - +export class InfraS3Validator extends S3Validator { static async fromStorageLocation( storageLocation: string, - ): Promise { - 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], pieces[2]); - const announcement = await s3Bucket.getS3Obj(ANNOUNCEMENT_KEY); - const address = announcement?.data.value.validator; - const mailbox = announcement?.data.value.mailbox_address; - const localDomain = announcement?.data.value.mailbox_domain; - - return new S3Validator( - address, - localDomain, - mailbox, - pieces[0], - pieces[1], - pieces[2], - ); - } - } - throw new Error(`Unable to parse location ${storageLocation}`); + ): Promise { + const inner = await S3Validator.fromStorageLocation(storageLocation); + return new InfraS3Validator(inner.validatorConfig, inner.s3Config); } - async getAnnouncement(): Promise { - const data = await this.s3Bucket.getS3Obj(ANNOUNCEMENT_KEY); - if (data) { - return data.data; - } - } - - async getLatestCheckpointIndex() { - const latestCheckpointIndex = await this.s3Bucket.getS3Obj( - LATEST_KEY, - ); - - if (!latestCheckpointIndex) return -1; - - return latestCheckpointIndex.data; - } - - async compare(other: S3Validator, count = 5): Promise { + async compare( + other: InfraS3Validator, + count = 5, + ): Promise { const latestCheckpointIndex = await this.s3Bucket.getS3Obj( LATEST_KEY, ); @@ -153,7 +102,7 @@ export class S3Validator extends BaseValidator { actual.data.messageId, ) ) { - const signerAddress = this.recoverAddressFromCheckpoint( + const signerAddress = InfraS3Validator.recoverAddressFromCheckpoint( actual.data.checkpoint, actual.data.signature, actual.data.messageId, @@ -196,10 +145,6 @@ export class S3Validator extends BaseValidator { return checkpointMetrics.slice(-1 * count); } - storageLocation(): string { - return `${LOCATION_PREFIX}/${this.s3Bucket.bucket}/${this.s3Bucket.region}`; - } - private async getCheckpointReceipt( index: number, ): Promise { diff --git a/typescript/infra/src/config/agent/validator.ts b/typescript/infra/src/config/agent/validator.ts index d424c76c3..61fdab217 100644 --- a/typescript/infra/src/config/agent/validator.ts +++ b/typescript/infra/src/config/agent/validator.ts @@ -4,6 +4,7 @@ import { ValidatorConfig as AgentValidatorConfig, ChainMap, ChainName, + S3Config, } from '@hyperlane-xyz/sdk'; import { ProtocolType } from '@hyperlane-xyz/utils'; @@ -75,11 +76,9 @@ export interface LocalCheckpointSyncerConfig { path: string; } -export interface S3CheckpointSyncerConfig { +export type S3CheckpointSyncerConfig = S3Config & { type: CheckpointSyncerType.S3; - bucket: string; - region: string; -} +}; export class ValidatorConfigHelper extends AgentConfigHelper { readonly #validatorsConfig: ValidatorBaseChainConfigMap; diff --git a/typescript/infra/src/config/gas-oracle.ts b/typescript/infra/src/config/gas-oracle.ts index 6ca80b39d..1994e91ce 100644 --- a/typescript/infra/src/config/gas-oracle.ts +++ b/typescript/infra/src/config/gas-oracle.ts @@ -129,9 +129,9 @@ function getMinUsdCost(local: ChainName, remote: ChainName): number { // By default, min cost is 20 cents let minUsdCost = 0.2; - // For Ethereum local, min cost is 2 USD + // For Ethereum local, min cost is 1.5 USD if (local === 'ethereum') { - minUsdCost = Math.max(minUsdCost, 2); + minUsdCost = Math.max(minUsdCost, 1.5); } const remoteMinCostOverrides: ChainMap = { diff --git a/typescript/infra/src/deployment/deploy.ts b/typescript/infra/src/deployment/deploy.ts index 7812dec3a..4a418def7 100644 --- a/typescript/infra/src/deployment/deploy.ts +++ b/typescript/infra/src/deployment/deploy.ts @@ -142,7 +142,12 @@ export async function writeAgentConfig( const deployedBlock = await mailbox.deployedBlock(); return deployedBlock.toNumber(); } catch (err) { - console.error('Failed to get deployed block for', chain, err); + console.error( + 'Failed to get deployed block, defaulting to 0. Chain:', + chain, + 'Error:', + err, + ); return 0; } }), diff --git a/typescript/infra/src/govern/HyperlaneAppGovernor.ts b/typescript/infra/src/govern/HyperlaneAppGovernor.ts index b48bc649f..187c1b700 100644 --- a/typescript/infra/src/govern/HyperlaneAppGovernor.ts +++ b/typescript/infra/src/govern/HyperlaneAppGovernor.ts @@ -3,7 +3,6 @@ import prompts from 'prompts'; import { Ownable__factory } from '@hyperlane-xyz/core'; import { - AccountConfig, ChainMap, ChainName, HyperlaneApp, @@ -115,7 +114,9 @@ export abstract class HyperlaneAppGovernor< if (calls.length > 0) { const confirmed = await summarizeCalls(submissionType, calls); if (confirmed) { - console.log(`Submitting calls on ${chain} via ${submissionType}`); + console.log( + `Submitting calls on ${chain} via ${SubmissionType[submissionType]}`, + ); await multiSend.sendTransactions( calls.map((call) => ({ to: call.to, @@ -125,7 +126,7 @@ export abstract class HyperlaneAppGovernor< ); } else { console.log( - `Skipping submission of calls on ${chain} via ${submissionType}`, + `Skipping submission of calls on ${chain} via ${SubmissionType[submissionType]}`, ); } } @@ -135,12 +136,7 @@ export abstract class HyperlaneAppGovernor< SubmissionType.SIGNER, new SignerMultiSend(this.checker.multiProvider, chain), ); - let safeOwner: Address; - if (typeof this.checker.configMap[chain].owner === 'string') { - safeOwner = this.checker.configMap[chain].owner as Address; - } else { - safeOwner = (this.checker.configMap[chain].owner as AccountConfig).owner; - } + const safeOwner = this.checker.configMap[chain].owner; await sendCallsForType( SubmissionType.SAFE, new SafeMultiSend(this.checker.multiProvider, chain, safeOwner), diff --git a/typescript/infra/test/agent-configs.test.ts b/typescript/infra/test/agent-configs.test.ts index ced36006a..679dbfc2d 100644 --- a/typescript/infra/test/agent-configs.test.ts +++ b/typescript/infra/test/agent-configs.test.ts @@ -43,7 +43,9 @@ describe('Agent configs', () => { const agentJsonConfigChains = Object.keys( config.agentJsonConfig.chains, ); - expect(agentJsonConfigChains).to.have.members( + // Allow for the agent JSON config to be a superset of the supported + // chain names, as AW may not always run agents for all chains. + expect(agentJsonConfigChains).to.include.members( config.supportedChainNames, ); }); diff --git a/typescript/infra/test/govern.hardhat-test.ts b/typescript/infra/test/govern.hardhat-test.ts index 07f94c0ce..e5852d1f3 100644 --- a/typescript/infra/test/govern.hardhat-test.ts +++ b/typescript/infra/test/govern.hardhat-test.ts @@ -134,6 +134,12 @@ describe('ICA governance', async () => { localRouter: remote.address, }; + accountOwner = await resolveOrDeployAccountOwner( + multiProvider, + remoteChain, + accountConfig, + ); + const recipientF = new TestRecipient__factory(signer); recipient = await recipientF.deploy(); @@ -145,23 +151,15 @@ describe('ICA governance', async () => { recipient, }, }; - // missing ica const configMap = { [localChain]: { owner: signer.address }, - [remoteChain]: { - owner: { origin: TestChainName.test1, owner: signer.address }, - }, + [remoteChain]: { owner: accountOwner }, }; const app = new TestApp(contractsMap, multiProvider); const checker = new TestChecker(multiProvider, app, configMap); governor = new HyperlaneTestGovernor(checker, icaApp); - accountOwner = await resolveOrDeployAccountOwner( - multiProvider, - remoteChain, - accountConfig, - ); await recipient.transferOwnership(accountOwner); }); diff --git a/typescript/sdk/CHANGELOG.md b/typescript/sdk/CHANGELOG.md index 152e40bd6..b484d11bc 100644 --- a/typescript/sdk/CHANGELOG.md +++ b/typescript/sdk/CHANGELOG.md @@ -1,5 +1,21 @@ # @hyperlane-xyz/sdk +## 3.13.0 + +### Minor Changes + +- 39ea7cdef: Implement multi collateral warp routes +- babe816f8: Support xERC20 and xERC20 Lockbox in SDK and CLI +- 0cf692e73: Implement metadata builder fetching from message + +### Patch Changes + +- Updated dependencies [babe816f8] +- Updated dependencies [b440d98be] +- Updated dependencies [0cf692e73] + - @hyperlane-xyz/core@3.13.0 + - @hyperlane-xyz/utils@3.13.0 + ## 3.12.0 ### Minor Changes diff --git a/typescript/sdk/README.md b/typescript/sdk/README.md index 4c704aa12..d662581f0 100644 --- a/typescript/sdk/README.md +++ b/typescript/sdk/README.md @@ -18,12 +18,6 @@ Note, this package uses [ESM Modules](https://gist.github.com/sindresorhus/a3978 ## Contents -### Constants - -The names and relevant metadata for all Hyperlane-supported chains are included in this SDK, including public RPC and Explorer urls. It also includes the addresses for all Hyperlane core contracts and middleware. - -### Classes for development, deployment, and testing - The SDK includes various classes for building, deploying, and testing multi-chain applications. Different abstractions serve different use cases. A few common utilities include: - `MultiProvider` / `MultiProtocolProvider`: A utility for managing chain metadata, and RPC providers. @@ -32,15 +26,6 @@ The SDK includes various classes for building, deploying, and testing multi-chai - `HyperlaneDeployer`: The base class for executing multi-chain contract deployments. - `Token` & `WarpCore`: Utilities for interacting with Warp Route deployments. -### Chain Logos - -The SDK contains SVG files for all Hyperlane-supported chains. They can be imported from the `/logos` folder. - -```js -import ArbitrumBlack from '@hyperlane-xyz/sdk/logos/black/arbitrum.svg'; -import ArbitrumColor from '@hyperlane-xyz/sdk/logos/color/arbitrum.svg'; -``` - ## License Apache 2.0 diff --git a/typescript/sdk/package.json b/typescript/sdk/package.json index c0fa21462..ef58f43cd 100644 --- a/typescript/sdk/package.json +++ b/typescript/sdk/package.json @@ -1,12 +1,13 @@ { "name": "@hyperlane-xyz/sdk", "description": "The official SDK for the Hyperlane Network", - "version": "3.12.2", + "version": "3.13.0", "dependencies": { + "@aws-sdk/client-s3": "^3.74.0", "@cosmjs/cosmwasm-stargate": "^0.31.3", "@cosmjs/stargate": "^0.31.3", - "@hyperlane-xyz/core": "3.12.2", - "@hyperlane-xyz/utils": "3.12.2", + "@hyperlane-xyz/core": "3.13.0", + "@hyperlane-xyz/utils": "3.13.0", "@safe-global/api-kit": "1.3.0", "@safe-global/protocol-kit": "1.3.0", "@solana/spl-token": "^0.3.8", diff --git a/typescript/infra/src/agents/aws/s3.ts b/typescript/sdk/src/aws/s3.ts similarity index 53% rename from typescript/infra/src/agents/aws/s3.ts rename to typescript/sdk/src/aws/s3.ts index 2c12cd00c..a222617b3 100644 --- a/typescript/infra/src/agents/aws/s3.ts +++ b/typescript/sdk/src/aws/s3.ts @@ -11,38 +11,60 @@ export interface S3Receipt { modified: Date; } +export interface S3Config { + bucket: string; + region: string; + folder?: string; + caching?: boolean; +} + export class S3Wrapper { private readonly client: S3Client; - readonly bucket: string; - readonly region: string; - readonly folder: string | undefined; + + private cache: Record> | 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], undefined); + return new S3Wrapper({ + bucket: match[1], + region: match[2], + caching: true, + }); + } + + constructor(readonly config: S3Config) { + this.client = new S3Client(config); + if (config.caching) { + this.cache = {}; + } } - constructor(bucket: string, region: string, folder: string | undefined) { - this.bucket = bucket; - this.region = region; - this.folder = folder; - this.client = new S3Client({ region }); + formatKey(key: string): string { + return this.config.folder ? `${this.config.folder}/${key}` : key; } async getS3Obj(key: string): Promise | undefined> { - const Key = this.folder ? `${this.folder}/${key}` : key; + const Key = this.formatKey(key); + if (this.cache?.[Key]) { + return this.cache![Key]; + } + const command = new GetObjectCommand({ - Bucket: this.bucket, + Bucket: this.config.bucket, Key, }); try { const response = await this.client.send(command); const body: string = await streamToString(response.Body as Readable); - return { + const result = { data: JSON.parse(body), modified: response.LastModified!, }; + if (this.cache) { + this.cache[Key] = result; + } + return result; } catch (e: any) { if (e.message.includes('The specified key does not exist.')) { return; @@ -50,4 +72,9 @@ export class S3Wrapper { throw e; } } + + url(key: string): string { + const Key = this.formatKey(key); + return `https://${this.config.bucket}.${this.config.region}.s3.amazonaws.com/${Key}`; + } } diff --git a/typescript/sdk/src/aws/validator.ts b/typescript/sdk/src/aws/validator.ts new file mode 100644 index 000000000..a60c05b0a --- /dev/null +++ b/typescript/sdk/src/aws/validator.ts @@ -0,0 +1,101 @@ +import { + Announcement, + BaseValidator, + S3Announcement, + S3CheckpointWithId, + ValidatorConfig, + isS3CheckpointWithId, +} from '@hyperlane-xyz/utils'; + +import { S3Config, S3Wrapper } from './s3.js'; + +const checkpointWithMessageIdKey = (checkpointIndex: number) => + `checkpoint_${checkpointIndex}_with_id.json`; +const LATEST_KEY = 'checkpoint_latest_index.json'; +const ANNOUNCEMENT_KEY = 'announcement.json'; +const LOCATION_PREFIX = 's3://'; + +/** + * Extension of BaseValidator that includes AWS S3 utilities. + */ +export class S3Validator extends BaseValidator { + public s3Bucket: S3Wrapper; + + constructor( + public validatorConfig: ValidatorConfig, + public s3Config: S3Config, + ) { + super(validatorConfig); + this.s3Bucket = new S3Wrapper(s3Config); + } + + static async fromStorageLocation( + storageLocation: string, + ): Promise { + if (storageLocation.startsWith(LOCATION_PREFIX)) { + const suffix = storageLocation.slice(LOCATION_PREFIX.length); + const pieces = suffix.split('/'); + if (pieces.length >= 2) { + const s3Config = { + bucket: pieces[0], + region: pieces[1], + folder: pieces[2], + caching: true, + }; + const s3Bucket = new S3Wrapper(s3Config); + const announcement = await s3Bucket.getS3Obj( + ANNOUNCEMENT_KEY, + ); + if (!announcement) { + throw new Error('No announcement found'); + } + + const validatorConfig = { + address: announcement.data.value.validator, + localDomain: announcement.data.value.mailbox_domain, + mailbox: announcement.data.value.mailbox_address, + }; + + return new S3Validator(validatorConfig, s3Config); + } + } + throw new Error(`Unable to parse location ${storageLocation}`); + } + + async getAnnouncement(): Promise { + const resp = await this.s3Bucket.getS3Obj(ANNOUNCEMENT_KEY); + if (!resp) { + throw new Error('No announcement found'); + } + + return resp.data.value; + } + + async getCheckpoint(index: number): Promise { + const key = checkpointWithMessageIdKey(index); + const s3Object = await this.s3Bucket.getS3Obj(key); + if (!s3Object) { + return; + } + + if (isS3CheckpointWithId(s3Object.data)) { + return s3Object.data; + } else { + throw new Error('Failed to parse checkpoint'); + } + } + + async getLatestCheckpointIndex(): Promise { + const latestCheckpointIndex = await this.s3Bucket.getS3Obj( + LATEST_KEY, + ); + + if (!latestCheckpointIndex) return -1; + + return latestCheckpointIndex.data; + } + + storageLocation(): string { + return `${LOCATION_PREFIX}/${this.s3Bucket.config.bucket}/${this.s3Bucket.config.region}`; + } +} diff --git a/typescript/sdk/src/consts/multisigIsm.ts b/typescript/sdk/src/consts/multisigIsm.ts index d384e073d..c6f8d8568 100644 --- a/typescript/sdk/src/consts/multisigIsm.ts +++ b/typescript/sdk/src/consts/multisigIsm.ts @@ -57,6 +57,7 @@ export const defaultMultisigConfigs: ChainMap = { validators: [ '0xf20c0b09f597597c8d2430d3d72dfddaf09177d1', '0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36', // mitosis + '0xae53467a5c2a9d9420c188d10fef5e1d9b9a5b80', // superform ], }, @@ -231,6 +232,14 @@ export const defaultMultisigConfigs: ChainMap = { ], }, + redstone: { + threshold: 2, + validators: [ + '0x1400b9737007f7978d8b4bbafb4a69c83f0641a7', + '0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f', // merkly + ], + }, + scroll: { threshold: 2, validators: [ @@ -281,4 +290,12 @@ export const defaultMultisigConfigs: ChainMap = { '0x1f87c368f8e05a85ef9126d984a980a20930cb9c', ], }, + + zetachain: { + threshold: 2, + validators: [ + '0xa3bca0b80317dbf9c7dce16a16ac89f4ff2b23ef', + '0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f', // merkly + ], + }, }; diff --git a/typescript/sdk/src/core/CoreDeployer.hardhat-test.ts b/typescript/sdk/src/core/CoreDeployer.hardhat-test.ts index 41fd03add..58bf38f27 100644 --- a/typescript/sdk/src/core/CoreDeployer.hardhat-test.ts +++ b/typescript/sdk/src/core/CoreDeployer.hardhat-test.ts @@ -9,7 +9,7 @@ import { TestChainName, testChains } from '../consts/testChains.js'; import { HyperlaneContractsMap } from '../contracts/types.js'; import { HyperlaneProxyFactoryDeployer } from '../deploy/HyperlaneProxyFactoryDeployer.js'; import { HookConfig } from '../hook/types.js'; -import { DerivedIsmConfigWithAddress } from '../ism/EvmIsmReader.js'; +import { DerivedIsmConfig } from '../ism/EvmIsmReader.js'; import { HyperlaneIsmFactory } from '../ism/HyperlaneIsmFactory.js'; import { AggregationIsmConfig, IsmType } from '../ism/types.js'; import { MultiProvider } from '../providers/MultiProvider.js'; @@ -141,9 +141,9 @@ describe('core', async () => { // Cast because we don't expect the 'string' type const defaultIsmOnchain = - coreConfigOnChain.defaultIsm as DerivedIsmConfigWithAddress; + coreConfigOnChain.defaultIsm as DerivedIsmConfig; const defaultIsmTest = coreConfig[chainName] - .defaultIsm as DerivedIsmConfigWithAddress; + .defaultIsm as DerivedIsmConfig; expect(defaultIsmOnchain.type).to.be.equal(defaultIsmTest.type); }), diff --git a/typescript/sdk/src/core/HyperlaneCore.ts b/typescript/sdk/src/core/HyperlaneCore.ts index f3cb21877..92f0e3023 100644 --- a/typescript/sdk/src/core/HyperlaneCore.ts +++ b/typescript/sdk/src/core/HyperlaneCore.ts @@ -1,3 +1,4 @@ +import { TransactionReceipt } from '@ethersproject/providers'; import { ethers } from 'ethers'; import type { TransactionReceipt as ViemTxReceipt } from 'viem'; @@ -6,6 +7,7 @@ import { Address, AddressBytes32, ProtocolType, + addressToBytes32, bytes32ToAddress, eqAddress, messageId, @@ -19,10 +21,7 @@ import { HyperlaneApp } from '../app/HyperlaneApp.js'; import { appFromAddressesMapHelper } from '../contracts/contracts.js'; import { HyperlaneAddressesMap } from '../contracts/types.js'; import { OwnableConfig } from '../deploy/types.js'; -import { - DerivedIsmConfigWithAddress, - EvmIsmReader, -} from '../ism/EvmIsmReader.js'; +import { DerivedIsmConfig, EvmIsmReader } from '../ism/EvmIsmReader.js'; import { IsmType, ModuleType, ismTypeToModuleType } from '../ism/types.js'; import { MultiProvider } from '../providers/MultiProvider.js'; import { RouterConfig } from '../router/types.js'; @@ -68,11 +67,19 @@ export class HyperlaneCore extends HyperlaneApp { destination: ChainName, recipient: AddressBytes32, body: string, + metadata?: string, + hook?: Address, ): Promise => { const destinationId = this.multiProvider.getDomainId(destination); return this.contractsMap[origin].mailbox[ - 'quoteDispatch(uint32,bytes32,bytes)' - ](destinationId, recipient, body); + 'quoteDispatch(uint32,bytes32,bytes,bytes,address)' + ]( + destinationId, + recipient, + body, + metadata || '0x', + hook || ethers.constants.AddressZero, + ); }; protected getDestination(message: DispatchedMessage): ChainName { @@ -87,7 +94,7 @@ export class HyperlaneCore extends HyperlaneApp { async getRecipientIsmConfig( message: DispatchedMessage, - ): Promise { + ): Promise { const destinationChain = this.getDestination(message); const ismReader = new EvmIsmReader(this.multiProvider, destinationChain); const address = await this.getRecipientIsmAddress(message); @@ -121,6 +128,42 @@ export class HyperlaneCore extends HyperlaneApp { } } + async sendMessage( + origin: ChainName, + destination: ChainName, + recipient: Address, + body: string, + hook?: Address, + metadata?: string, + ): Promise<{ dispatchTx: TransactionReceipt; message: DispatchedMessage }> { + const mailbox = this.getContracts(origin).mailbox; + const destinationDomain = this.multiProvider.getDomainId(destination); + const recipientBytes32 = addressToBytes32(recipient); + const quote = await this.quoteGasPayment( + origin, + destination, + recipientBytes32, + body, + metadata, + hook, + ); + const dispatchTx = await this.multiProvider.handleTx( + origin, + mailbox['dispatch(uint32,bytes32,bytes,bytes,address)']( + destinationDomain, + recipientBytes32, + body, + metadata || '0x', + hook || ethers.constants.AddressZero, + { value: quote }, + ), + ); + return { + dispatchTx, + message: this.getDispatchedMessages(dispatchTx)[0], + }; + } + async relayMessage( message: DispatchedMessage, ): Promise { @@ -212,7 +255,14 @@ export class HyperlaneCore extends HyperlaneApp { getDispatchedMessages( sourceTx: ethers.ContractReceipt | ViemTxReceipt, ): DispatchedMessage[] { - return HyperlaneCore.getDispatchedMessages(sourceTx); + const messages = HyperlaneCore.getDispatchedMessages(sourceTx); + return messages.map(({ parsed, ...other }) => { + const originChain = + this.multiProvider.tryGetChainName(parsed.origin) ?? undefined; + const destinationChain = + this.multiProvider.tryGetChainName(parsed.destination) ?? undefined; + return { parsed: { ...parsed, originChain, destinationChain }, ...other }; + }); } async getDispatchTx( diff --git a/typescript/sdk/src/core/HyperlaneCoreDeployer.ts b/typescript/sdk/src/core/HyperlaneCoreDeployer.ts index dc36a05b9..6a06fa88f 100644 --- a/typescript/sdk/src/core/HyperlaneCoreDeployer.ts +++ b/typescript/sdk/src/core/HyperlaneCoreDeployer.ts @@ -32,12 +32,14 @@ export class HyperlaneCoreDeployer extends HyperlaneDeployer< multiProvider: MultiProvider, readonly ismFactory: HyperlaneIsmFactory, contractVerifier?: ContractVerifier, + concurrentDeploy: boolean = false, ) { super(multiProvider, coreFactories, { logger: rootLogger.child({ module: 'CoreDeployer' }), chainTimeoutMs: 1000 * 60 * 10, // 10 minutes ismFactory, contractVerifier, + concurrentDeploy, }); this.hookDeployer = new HyperlaneHookDeployer( multiProvider, @@ -69,11 +71,6 @@ export class HyperlaneCoreDeployer extends HyperlaneDeployer< proxyAdmin, [domain], ); - // resolve the owner account so that the subsequent calls terminate early - config.owner = await this.resolveInterchainAccountAsOwner( - chain, - config.owner, - ); let defaultIsm = await mailbox.defaultIsm(); const matches = await moduleMatchesConfig( @@ -111,15 +108,11 @@ export class HyperlaneCoreDeployer extends HyperlaneDeployer< // configure mailbox try { - const owner = await this.resolveInterchainAccountAsOwner( - chain, - config.owner, - ); this.logger.debug('Initializing mailbox'); await this.multiProvider.handleTx( chain, mailbox.initialize( - owner, + config.owner, defaultIsm, defaultHook.address, requiredHook.address, @@ -133,7 +126,9 @@ export class HyperlaneCoreDeployer extends HyperlaneDeployer< !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') + !e.error?.reason?.includes('already initialized') && + // Some providers, like on Viction, return a generic error message for all revert reasons + !e.message.includes('always failing transaction') ) { throw e; } diff --git a/typescript/sdk/src/deploy/HyperlaneDeployer.ts b/typescript/sdk/src/deploy/HyperlaneDeployer.ts index 46f4db67e..d9fefa0e1 100644 --- a/typescript/sdk/src/deploy/HyperlaneDeployer.ts +++ b/typescript/sdk/src/deploy/HyperlaneDeployer.ts @@ -43,7 +43,7 @@ import { proxyConstructorArgs, proxyImplementation, } from './proxy.js'; -import { OwnableConfig, Owner } from './types.js'; +import { OwnableConfig } from './types.js'; import { ContractVerifier } from './verify/ContractVerifier.js'; import { ContractVerificationInput, @@ -60,6 +60,7 @@ export interface DeployerOptions { ismFactory?: HyperlaneIsmFactory; icaApp?: InterchainAccount; contractVerifier?: ContractVerifier; + concurrentDeploy?: boolean; } export abstract class HyperlaneDeployer< @@ -82,7 +83,7 @@ export abstract class HyperlaneDeployer< protected readonly icaAddresses = {}, ) { this.logger = options?.logger ?? rootLogger.child({ module: 'deployer' }); - this.chainTimeoutMs = options?.chainTimeoutMs ?? 5 * 60 * 1000; // 5 minute timeout per chain + this.chainTimeoutMs = options?.chainTimeoutMs ?? 15 * 60 * 1000; // 15 minute timeout per chain this.options.ismFactory?.setDeployer(this); if (Object.keys(icaAddresses).length > 0) { this.options.icaApp = InterchainAccount.fromAddressesMap( @@ -125,6 +126,8 @@ export abstract class HyperlaneDeployer< ).intersection; this.logger.debug(`Start deploy to ${targetChains}`); + + const deployPromises = []; for (const chain of targetChains) { const signerUrl = await this.multiProvider.tryGetExplorerAddressUrl( chain, @@ -135,11 +138,31 @@ export abstract class HyperlaneDeployer< this.startingBlockNumbers[chain] = await this.multiProvider .getProvider(chain) .getBlockNumber(); - await runWithTimeout(this.chainTimeoutMs, async () => { + + const deployPromise = runWithTimeout(this.chainTimeoutMs, async () => { const contracts = await this.deployContracts(chain, configMap[chain]); this.addDeployedContracts(chain, contracts); + this.logger.info({ chain }, 'Successfully deployed contracts'); }); + if (this.options.concurrentDeploy) { + deployPromises.push(deployPromise); + } else { + await deployPromise; + } + } + + // Await all deploy promises. If concurrent deploy is not enabled, this will be a no-op. + const deployResults = await Promise.allSettled(deployPromises); + for (const [i, result] of deployResults.entries()) { + if (result.status === 'rejected') { + this.logger.error( + { chain: targetChains[i], error: result.reason }, + 'Deployment failed', + ); + throw result.reason; + } } + return this.deployedContracts; } @@ -709,13 +732,10 @@ export abstract class HyperlaneDeployer< continue; } const current = await ownable.owner(); - const owner = await this.resolveInterchainAccountAsOwner( - chain, - config.ownerOverrides?.[contractName as K] ?? config.owner, - ); + const owner = config.ownerOverrides?.[contractName as K] ?? config.owner; if (!eqAddress(current, owner)) { this.logger.debug( - { contractName }, + { contractName, current, desiredOwner: owner }, 'Current owner and config owner do not match', ); const receipt = await this.runIfOwner(chain, ownable, () => { @@ -736,24 +756,4 @@ export abstract class HyperlaneDeployer< return receipts.filter((x) => !!x) as ethers.ContractReceipt[]; } - - protected async resolveInterchainAccountAsOwner( - chain: ChainName, - owner: Owner, - ): Promise
{ - if (typeof owner === 'string') { - return owner; - } else { - const routerAddress = this.options.icaApp?.routerAddress(chain); - if (!routerAddress) { - throw new Error('InterchainAccountRouter not deployed'); - } - const router = InterchainAccount.fromAddressesMap( - { chain: { router: routerAddress } }, - this.multiProvider, - ); - // submits network transaction to deploy the account iff it doesn't exist - return router.deployAccount(chain, owner); - } - } } diff --git a/typescript/sdk/src/deploy/schemas.ts b/typescript/sdk/src/deploy/schemas.ts index 7d965f0bc..99b106652 100644 --- a/typescript/sdk/src/deploy/schemas.ts +++ b/typescript/sdk/src/deploy/schemas.ts @@ -1,8 +1,6 @@ import { z } from 'zod'; -import { AccountConfigSchema } from '../middleware/account/schemas.js'; - -export const OwnerSchema = z.union([z.string(), AccountConfigSchema]); +export const OwnerSchema = z.string(); export const OwnableConfigSchema = z.object({ owner: OwnerSchema, diff --git a/typescript/sdk/src/deploy/types.ts b/typescript/sdk/src/deploy/types.ts index 084979d0e..5c74845a4 100644 --- a/typescript/sdk/src/deploy/types.ts +++ b/typescript/sdk/src/deploy/types.ts @@ -39,7 +39,7 @@ export async function resolveOrDeployAccountOwner( throw new Error('localRouter is required for AccountConfig'); } // submits a transaction to deploy an interchain account if the owner is an AccountConfig and the ICA isn't not deployed yet - return await deployInterchainAccount(multiProvider, chain, owner); + return deployInterchainAccount(multiProvider, chain, owner); } } diff --git a/typescript/sdk/src/deploy/verify/ContractVerifier.ts b/typescript/sdk/src/deploy/verify/ContractVerifier.ts index f6baa1c16..e43646396 100644 --- a/typescript/sdk/src/deploy/verify/ContractVerifier.ts +++ b/typescript/sdk/src/deploy/verify/ContractVerifier.ts @@ -137,7 +137,7 @@ export class ContractVerifier { break; default: errorMessage = `Verification failed. ${ - result.result ?? response.statusText + JSON.stringify(result.result) ?? response.statusText }`; break; } @@ -155,7 +155,7 @@ export class ContractVerifier { if (result.result === ExplorerApiErrors.UNABLE_TO_VERIFY) { const errorMessage = `Verification failed. ${ - result.result ?? response.statusText + JSON.stringify(result.result) ?? response.statusText }`; verificationLogger.debug(errorMessage); throw new Error(`[${chain}] ${errorMessage}`); diff --git a/typescript/sdk/src/hook/EvmHookReader.ts b/typescript/sdk/src/hook/EvmHookReader.ts index 32436f453..c5db5165c 100644 --- a/typescript/sdk/src/hook/EvmHookReader.ts +++ b/typescript/sdk/src/hook/EvmHookReader.ts @@ -42,6 +42,8 @@ import { RoutingHookConfig, } from './types.js'; +export type DerivedHookConfig = WithAddress; + export interface HookReader { deriveHookConfig(address: Address): Promise>; deriveMerkleTreeConfig( @@ -82,9 +84,10 @@ export class EvmHookReader implements HookReader { this.provider = multiProvider.getProvider(chain); } - async deriveHookConfig(address: Address): Promise> { + async deriveHookConfig(address: Address): Promise { const hook = IPostDispatchHook__factory.connect(address, this.provider); const onchainHookType: OnchainHookType = await hook.hookType(); + this.logger.debug('Deriving HookConfig', { address, onchainHookType }); switch (onchainHookType) { case OnchainHookType.ROUTING: diff --git a/typescript/sdk/src/hook/HyperlaneHookDeployer.ts b/typescript/sdk/src/hook/HyperlaneHookDeployer.ts index 3e521b094..d1877c930 100644 --- a/typescript/sdk/src/hook/HyperlaneHookDeployer.ts +++ b/typescript/sdk/src/hook/HyperlaneHookDeployer.ts @@ -115,7 +115,7 @@ export class HyperlaneHookDeployer extends HyperlaneDeployer< config.maxProtocolFee, config.protocolFee, config.beneficiary, - await this.resolveInterchainAccountAsOwner(chain, config.owner), + config.owner, ]); } @@ -313,12 +313,20 @@ export class HyperlaneHookDeployer extends HyperlaneDeployer< } const overrides = this.multiProvider.getTransactionOverrides(chain); - await this.runIfOwner(chain, routingHook, async () => - this.multiProvider.handleTx( + await this.runIfOwner(chain, routingHook, async () => { + this.logger.debug( + { + chain, + routingHookAddress: routingHook.address, + routingConfigs, + }, + 'Setting routing hooks', + ); + return this.multiProvider.handleTx( chain, routingHook.setHooks(routingConfigs, overrides), - ), - ); + ); + }); await this.transferOwnershipOfContracts(chain, config, { [config.type]: routingHook, diff --git a/typescript/sdk/src/index.ts b/typescript/sdk/src/index.ts index fd338351e..79f780df7 100644 --- a/typescript/sdk/src/index.ts +++ b/typescript/sdk/src/index.ts @@ -340,8 +340,6 @@ export { MailboxClientConfig as ConnectionClientConfig, ClientViolation as ConnectionClientViolation, ClientViolationType as ConnectionClientViolationType, - ForeignDeploymentConfig, - GasConfig, GasRouterConfig, MailboxClientConfig, ProxiedFactories, @@ -419,27 +417,7 @@ export { } from './token/adapters/serialization.js'; export { HypERC20App } from './token/app.js'; export { HypERC20Checker } from './token/checker.js'; -export { - CollateralConfig, - ERC20Metadata, - ERC20RouterConfig, - ERC721RouterConfig, - HypERC20CollateralConfig, - HypERC20Config, - HypERC721CollateralConfig, - HypERC721Config, - HypNativeConfig, - MinimalTokenMetadata, - NativeConfig, - SyntheticConfig, - TokenConfig, - TokenMetadata, - TokenType, - isCollateralConfig, - isNativeConfig, - isSyntheticConfig, - isUriConfig, -} from './token/config.js'; +export { TokenType } from './token/config.js'; export { HypERC20Factories, HypERC721Factories, @@ -480,9 +458,18 @@ export { AggregationIsmConfigSchema } from './ism/schemas.js'; export { MailboxClientConfigSchema as mailboxClientConfigSchema } from './router/schemas.js'; export { WarpRouteDeployConfigSchema, - TokenRouterConfigSchema as tokenRouterConfigSchema, + TokenRouterConfigSchema, + CollateralConfig, + NativeConfig, + isCollateralConfig, + isNativeConfig, + isSyntheticConfig, + isTokenMetadata, } from './token/schemas.js'; +export { isCompliant } from './utils/schemas.js'; export { TokenRouterConfig, WarpRouteDeployConfig } from './token/types.js'; +export { S3Validator } from './aws/validator.js'; +export { S3Config, S3Wrapper, S3Receipt } from './aws/s3.js'; // prettier-ignore // @ts-ignore diff --git a/typescript/sdk/src/ism/EvmIsmReader.ts b/typescript/sdk/src/ism/EvmIsmReader.ts index 64483d431..b62fd321c 100644 --- a/typescript/sdk/src/ism/EvmIsmReader.ts +++ b/typescript/sdk/src/ism/EvmIsmReader.ts @@ -28,25 +28,14 @@ import { IsmType, ModuleType, MultisigIsmConfig, - OpStackIsmConfig, - PausableIsmConfig, + NullIsmConfig, RoutingIsmConfig, - TestIsmConfig, - TrustedRelayerIsmConfig, } from './types.js'; -type NullIsmConfig = - | PausableIsmConfig - | TestIsmConfig - | OpStackIsmConfig - | TrustedRelayerIsmConfig; - -export type DerivedIsmConfigWithAddress = WithAddress< - Exclude ->; +export type DerivedIsmConfig = WithAddress>; export interface IsmReader { - deriveIsmConfig(address: Address): Promise; + deriveIsmConfig(address: Address): Promise; deriveRoutingConfig(address: Address): Promise>; deriveAggregationConfig( address: Address, @@ -71,14 +60,13 @@ export class EvmIsmReader implements IsmReader { this.provider = multiProvider.getProvider(chain); } - async deriveIsmConfig( - address: Address, - ): Promise { + async deriveIsmConfig(address: Address): Promise { const ism = IInterchainSecurityModule__factory.connect( address, this.provider, ); const moduleType: ModuleType = await ism.moduleType(); + this.logger.debug('Deriving ISM config', { address, moduleType }); switch (moduleType) { case ModuleType.UNUSED: @@ -116,12 +104,18 @@ export class EvmIsmReader implements IsmReader { const domainIds = await ism.domains(); await concurrentMap(this.concurrency, domainIds, async (domainId) => { - const chainName = this.multiProvider.getChainName(domainId.toNumber()); + const chainName = this.multiProvider.tryGetChainName(domainId.toNumber()); + if (!chainName) { + this.logger.warn( + `Unknown domain ID ${domainId}, skipping domain configuration`, + ); + return; + } const module = await ism.module(domainId); domains[chainName] = await this.deriveIsmConfig(module); }); - // Fallback routing ISM extends from MailboxClient, default routign + // Fallback routing ISM extends from MailboxClient, default routing let ismType = IsmType.FALLBACK_ROUTING; try { await ism.mailbox(); diff --git a/typescript/sdk/src/ism/HyperlaneIsmFactory.hardhat-test.ts b/typescript/sdk/src/ism/HyperlaneIsmFactory.hardhat-test.ts index 2fe0b8f34..f535277e4 100644 --- a/typescript/sdk/src/ism/HyperlaneIsmFactory.hardhat-test.ts +++ b/typescript/sdk/src/ism/HyperlaneIsmFactory.hardhat-test.ts @@ -3,14 +3,14 @@ import { expect } from 'chai'; import hre from 'hardhat'; import { DomainRoutingIsm, TrustedRelayerIsm } from '@hyperlane-xyz/core'; -import { Address } from '@hyperlane-xyz/utils'; +import { Address, randomElement, randomInt } from '@hyperlane-xyz/utils'; import { TestChainName, testChains } from '../consts/testChains.js'; import { TestCoreApp } from '../core/TestCoreApp.js'; import { TestCoreDeployer } from '../core/TestCoreDeployer.js'; import { HyperlaneProxyFactoryDeployer } from '../deploy/HyperlaneProxyFactoryDeployer.js'; import { MultiProvider } from '../providers/MultiProvider.js'; -import { randomAddress, randomInt } from '../test/testUtils.js'; +import { randomAddress } from '../test/testUtils.js'; import { HyperlaneIsmFactory } from './HyperlaneIsmFactory.js'; import { @@ -27,43 +27,58 @@ import { moduleMatchesConfig } from './utils.js'; function randomModuleType(): ModuleType { const choices = [ ModuleType.AGGREGATION, - ModuleType.MERKLE_ROOT_MULTISIG, + ModuleType.MESSAGE_ID_MULTISIG, ModuleType.ROUTING, ModuleType.NULL, ]; - return choices[randomInt(choices.length)]; + return randomElement(choices); } -const randomMultisigIsmConfig = (m: number, n: number): MultisigIsmConfig => { +const randomMultisigIsmConfig = ( + m: number, + n: number, + addresses?: string[], +): MultisigIsmConfig => { const emptyArray = new Array(n).fill(0); - const validators = emptyArray.map(() => randomAddress()); + const validators = emptyArray + .map(() => (addresses ? randomElement(addresses) : randomAddress())) + .sort(); return { - type: IsmType.MERKLE_ROOT_MULTISIG, + type: IsmType.MESSAGE_ID_MULTISIG, validators, threshold: m, }; }; -const randomIsmConfig = (depth = 0, maxDepth = 2): IsmConfig => { +export const randomIsmConfig = ( + maxDepth = 5, + validatorAddresses?: string[], + relayerAddress?: string, +): Exclude => { const moduleType = - depth == maxDepth ? ModuleType.MERKLE_ROOT_MULTISIG : randomModuleType(); - if (moduleType === ModuleType.MERKLE_ROOT_MULTISIG) { - const n = randomInt(5, 1); - return randomMultisigIsmConfig(randomInt(n, 1), n); + maxDepth === 0 ? ModuleType.MESSAGE_ID_MULTISIG : randomModuleType(); + if (moduleType === ModuleType.MESSAGE_ID_MULTISIG) { + const n = randomInt(validatorAddresses?.length ?? 5, 1); + return randomMultisigIsmConfig(randomInt(n, 1), n, validatorAddresses); } else if (moduleType === ModuleType.ROUTING) { const config: RoutingIsmConfig = { type: IsmType.ROUTING, owner: randomAddress(), domains: Object.fromEntries( - testChains.map((c) => [c, randomIsmConfig(depth + 1)]), + testChains.map((c) => [ + c, + randomIsmConfig(maxDepth - 1, validatorAddresses, relayerAddress), + ]), ), }; return config; } else if (moduleType === ModuleType.AGGREGATION) { - const n = randomInt(5, 1); + const n = randomInt(5, 2); const modules = new Array(n) .fill(0) - .map(() => randomIsmConfig(depth + 1)); + .map(() => + randomIsmConfig(maxDepth - 1, validatorAddresses, relayerAddress), + ); const config: AggregationIsmConfig = { type: IsmType.AGGREGATION, threshold: randomInt(n, 1), @@ -73,7 +88,7 @@ const randomIsmConfig = (depth = 0, maxDepth = 2): IsmConfig => { } else if (moduleType === ModuleType.NULL) { const config: TrustedRelayerIsmConfig = { type: IsmType.TRUSTED_RELAYER, - relayer: randomAddress(), + relayer: relayerAddress ?? randomAddress(), }; return config; } else { diff --git a/typescript/sdk/src/ism/metadata/aggregation.test.ts b/typescript/sdk/src/ism/metadata/aggregation.test.ts index cb6c3818c..13c06654d 100644 --- a/typescript/sdk/src/ism/metadata/aggregation.test.ts +++ b/typescript/sdk/src/ism/metadata/aggregation.test.ts @@ -1,21 +1,25 @@ import { expect } from 'chai'; -import { readFileSync, readdirSync } from 'fs'; +import { ethers } from 'ethers'; +import { existsSync, readFileSync, readdirSync } from 'fs'; + +import { IsmType } from '../types.js'; import { - AggregationIsmMetadata, - AggregationIsmMetadataBuilder, + AggregationMetadata, + AggregationMetadataBuilder, } from './aggregation.js'; import { Fixture } from './types.test.js'; const path = '../../solidity/fixtures/aggregation'; -const files = readdirSync(path); -const fixtures: Fixture[] = files +const files = existsSync(path) ? readdirSync(path) : []; +const fixtures: Fixture[] = files .map((f) => JSON.parse(readFileSync(`${path}/${f}`, 'utf8'))) .map((contents) => { const { encoded, ...values } = contents; return { encoded, decoded: { + type: IsmType.AGGREGATION, submoduleMetadata: Object.values(values), }, }; @@ -24,17 +28,21 @@ const fixtures: Fixture[] = files describe('AggregationMetadataBuilder', () => { fixtures.forEach((fixture, i) => { it(`should encode fixture ${i}`, () => { - expect(AggregationIsmMetadataBuilder.encode(fixture.decoded)).to.equal( + expect(AggregationMetadataBuilder.encode(fixture.decoded)).to.equal( fixture.encoded, ); }); it(`should decode fixture ${i}`, () => { + const count = fixture.decoded.submoduleMetadata.length; expect( - AggregationIsmMetadataBuilder.decode( - fixture.encoded, - fixture.decoded.submoduleMetadata.length, - ), + AggregationMetadataBuilder.decode(fixture.encoded, { + ism: { + type: IsmType.AGGREGATION, + modules: new Array(count).fill(ethers.constants.AddressZero), + threshold: count, + }, + } as any), ).to.deep.equal(fixture.decoded); }); }); diff --git a/typescript/sdk/src/ism/metadata/aggregation.ts b/typescript/sdk/src/ism/metadata/aggregation.ts index 8ddae22f6..52f725219 100644 --- a/typescript/sdk/src/ism/metadata/aggregation.ts +++ b/typescript/sdk/src/ism/metadata/aggregation.ts @@ -1,20 +1,91 @@ -import { fromHexString, toHexString } from '@hyperlane-xyz/utils'; +import { + WithAddress, + assert, + fromHexString, + rootLogger, + timeout, + toHexString, +} from '@hyperlane-xyz/utils'; + +import { DerivedIsmConfig } from '../EvmIsmReader.js'; +import { AggregationIsmConfig, IsmType } from '../types.js'; + +import { + BaseMetadataBuilder, + MetadataBuilder, + MetadataContext, + StructuredMetadata, +} from './builder.js'; // null indicates that metadata is NOT INCLUDED for this submodule // empty or 0x string indicates that metadata is INCLUDED but NULL -export interface AggregationIsmMetadata { - submoduleMetadata: Array; +export interface AggregationMetadata { + type: IsmType.AGGREGATION; + submoduleMetadata: Array; } const RANGE_SIZE = 4; // adapted from rust/agents/relayer/src/msg/metadata/aggregation.rs -export class AggregationIsmMetadataBuilder { +export class AggregationMetadataBuilder implements MetadataBuilder { + protected logger = rootLogger.child({ + module: 'AggregationIsmMetadataBuilder', + }); + + constructor(protected readonly base: BaseMetadataBuilder) {} + + async build( + context: MetadataContext>, + maxDepth = 10, + timeoutMs = maxDepth * 1000, + ): Promise { + this.logger.debug( + { context, maxDepth, timeoutMs }, + 'Building aggregation metadata', + ); + assert(maxDepth > 0, 'Max depth reached'); + const promises = await Promise.allSettled( + context.ism.modules.map((module) => + timeout( + this.base.build( + { + ...context, + ism: module as DerivedIsmConfig, + }, + maxDepth - 1, + ), + timeoutMs, + ), + ), + ); + const metadatas = promises.map((r) => + r.status === 'fulfilled' ? r.value ?? null : null, + ); + const included = metadatas.filter((m) => m !== null).length; + assert( + included >= context.ism.threshold, + `Only built ${included} of ${context.ism.threshold} required modules`, + ); + + // only include the first threshold metadatas + let count = 0; + for (let i = 0; i < metadatas.length; i++) { + if (metadatas[i] === null) continue; + count += 1; + if (count > context.ism.threshold) metadatas[i] = null; + } + + return AggregationMetadataBuilder.encode({ + ...context.ism, + submoduleMetadata: metadatas, + }); + } + static rangeIndex(index: number): number { return index * 2 * RANGE_SIZE; } - static encode(metadata: AggregationIsmMetadata): string { + static encode(metadata: AggregationMetadata): string { const rangeSize = this.rangeIndex(metadata.submoduleMetadata.length); let encoded = Buffer.alloc(rangeSize, 0); @@ -48,13 +119,19 @@ export class AggregationIsmMetadataBuilder { }; } - static decode(metadata: string, count: number): AggregationIsmMetadata { - const submoduleMetadata = []; - for (let i = 0; i < count; i++) { - const range = this.metadataRange(metadata, i); - const submeta = range.start > 0 ? range.encoded : null; - submoduleMetadata.push(submeta); - } - return { submoduleMetadata }; + static decode( + metadata: string, + context: MetadataContext, + ): AggregationMetadata { + const submoduleMetadata = context.ism.modules.map((ism, index) => { + const range = this.metadataRange(metadata, index); + if (range.start == 0) return null; + if (typeof ism === 'string') return range.encoded; + return BaseMetadataBuilder.decode(range.encoded, { + ...context, + ism: ism as DerivedIsmConfig, + }); + }); + return { type: IsmType.AGGREGATION, submoduleMetadata }; } } diff --git a/typescript/sdk/src/ism/metadata/builder.hardhat-test.ts b/typescript/sdk/src/ism/metadata/builder.hardhat-test.ts new file mode 100644 index 000000000..844a87df5 --- /dev/null +++ b/typescript/sdk/src/ism/metadata/builder.hardhat-test.ts @@ -0,0 +1,180 @@ +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers.js'; +import hre from 'hardhat'; +import { before } from 'mocha'; +import sinon from 'sinon'; + +import { MerkleTreeHook, TestRecipient } from '@hyperlane-xyz/core'; +import { + BaseValidator, + Checkpoint, + CheckpointWithId, + Domain, + S3CheckpointWithId, + addressToBytes32, + eqAddress, + objMap, + randomElement, +} from '@hyperlane-xyz/utils'; + +import { testChains } from '../../consts/testChains.js'; +import { serializeContractsMap } from '../../contracts/contracts.js'; +import { HyperlaneCore } from '../../core/HyperlaneCore.js'; +import { TestCoreDeployer } from '../../core/TestCoreDeployer.js'; +import { TestRecipientDeployer } from '../../core/TestRecipientDeployer.js'; +import { HyperlaneProxyFactoryDeployer } from '../../deploy/HyperlaneProxyFactoryDeployer.js'; +import { HyperlaneHookDeployer } from '../../hook/HyperlaneHookDeployer.js'; +import { HookType, MerkleTreeHookConfig } from '../../hook/types.js'; +import { MultiProvider } from '../../providers/MultiProvider.js'; +import { ChainName } from '../../types.js'; +import { EvmIsmReader } from '../EvmIsmReader.js'; +import { randomIsmConfig } from '../HyperlaneIsmFactory.hardhat-test.js'; +import { HyperlaneIsmFactory } from '../HyperlaneIsmFactory.js'; + +import { BaseMetadataBuilder, MetadataContext } from './builder.js'; + +const MAX_ISM_DEPTH = 5; +const MAX_NUM_VALIDATORS = 10; +const NUM_RUNS = 16; + +describe('BaseMetadataBuilder', () => { + let core: HyperlaneCore; + let ismFactory: HyperlaneIsmFactory; + let merkleHooks: Record; + let testRecipients: Record; + let relayer: SignerWithAddress; + let validators: SignerWithAddress[]; + let metadataBuilder: BaseMetadataBuilder; + + before(async () => { + [relayer, ...validators] = await hre.ethers.getSigners(); + const multiProvider = MultiProvider.createTestMultiProvider({ + signer: relayer, + }); + const ismFactoryDeployer = new HyperlaneProxyFactoryDeployer(multiProvider); + ismFactory = new HyperlaneIsmFactory( + await ismFactoryDeployer.deploy(multiProvider.mapKnownChains(() => ({}))), + multiProvider, + ); + + const coreDeployer = new TestCoreDeployer(multiProvider, ismFactory); + const recipientDeployer = new TestRecipientDeployer(multiProvider); + testRecipients = objMap( + await recipientDeployer.deploy( + Object.fromEntries(testChains.map((c) => [c, {}])), + ), + (_, { testRecipient }) => testRecipient, + ); + core = await coreDeployer.deployApp(); + const hookDeployer = new HyperlaneHookDeployer( + multiProvider, + serializeContractsMap(core.contractsMap), + ismFactory, + ); + const hookConfig = objMap( + core.chainMap, + (): MerkleTreeHookConfig => ({ + type: HookType.MERKLE_TREE, + }), + ); + const hookContracts = await hookDeployer.deploy(hookConfig); + merkleHooks = Object.fromEntries( + Object.entries(hookContracts).map(([chain, { merkleTreeHook }]) => [ + core.multiProvider.getDomainId(chain), + merkleTreeHook, + ]), + ); + + metadataBuilder = new BaseMetadataBuilder(core); + + sinon + .stub(metadataBuilder.multisigMetadataBuilder, 'getS3Checkpoints') + .callsFake( + async (multisigAddresses, match): Promise => { + const merkleHook = merkleHooks[match.origin]; + const checkpoint: Checkpoint = { + root: await merkleHook.root(), + merkle_tree_hook_address: addressToBytes32(merkleHook.address), + index: match.index, + mailbox_domain: match.origin, + }; + const checkpointWithId: CheckpointWithId = { + checkpoint, + message_id: match.messageId, + }; + const digest = BaseValidator.messageHash(checkpoint, match.messageId); + const checkpoints: S3CheckpointWithId[] = []; + for (const validator of multisigAddresses) { + const signature = await validators + .find((s) => eqAddress(s.address, validator))! + .signMessage(digest); + checkpoints.push({ value: checkpointWithId, signature }); + } + return checkpoints; + }, + ); + }); + + describe('#build', () => { + let origin: ChainName; + let destination: ChainName; + let context: MetadataContext; + let metadata: string; + + beforeEach(async () => { + origin = randomElement(testChains); + destination = randomElement(testChains.filter((c) => c !== origin)); + const testRecipient = testRecipients[destination]; + + const addresses = validators + .map((s) => s.address) + .slice(0, MAX_NUM_VALIDATORS); + const config = randomIsmConfig(MAX_ISM_DEPTH, addresses, relayer.address); + const deployedIsm = await ismFactory.deploy({ + destination, + config, + mailbox: core.getAddresses(destination).mailbox, + }); + await testRecipient.setInterchainSecurityModule(deployedIsm.address); + + const merkleHookAddress = + merkleHooks[core.multiProvider.getDomainId(origin)].address; + const { dispatchTx, message } = await core.sendMessage( + origin, + destination, + testRecipient.address, + '0xdeadbeef', + merkleHookAddress, + ); + + const derivedIsm = await new EvmIsmReader( + core.multiProvider, + destination, + ).deriveIsmConfig(deployedIsm.address); + + context = { + hook: { + type: HookType.MERKLE_TREE, + address: merkleHookAddress, + }, + ism: derivedIsm, + message, + dispatchTx, + }; + + metadata = await metadataBuilder.build(context, MAX_ISM_DEPTH); + }); + + for (let i = 0; i < NUM_RUNS; i++) { + it(`should build valid metadata for random ism config (${i})`, async () => { + // must call process for trusted relayer to be able to verify + await core + .getContracts(destination) + .mailbox.process(metadata, context.message.message); + }); + + it(`should decode metadata for random ism config (${i})`, async () => { + BaseMetadataBuilder.decode(metadata, context); + }); + } + }); +}); diff --git a/typescript/sdk/src/ism/metadata/builder.ts b/typescript/sdk/src/ism/metadata/builder.ts new file mode 100644 index 000000000..66e364bbc --- /dev/null +++ b/typescript/sdk/src/ism/metadata/builder.ts @@ -0,0 +1,133 @@ +/* eslint-disable no-case-declarations */ +import { TransactionReceipt } from '@ethersproject/providers'; + +import { WithAddress, assert, rootLogger } from '@hyperlane-xyz/utils'; + +import { deepFind } from '../../../../utils/dist/objects.js'; +import { HyperlaneCore } from '../../core/HyperlaneCore.js'; +import { DispatchedMessage } from '../../core/types.js'; +import { DerivedHookConfig } from '../../hook/EvmHookReader.js'; +import { HookType, MerkleTreeHookConfig } from '../../hook/types.js'; +import { MultiProvider } from '../../providers/MultiProvider.js'; +import { DerivedIsmConfig } from '../EvmIsmReader.js'; +import { IsmType } from '../types.js'; + +import { + AggregationMetadata, + AggregationMetadataBuilder, +} from './aggregation.js'; +import { MultisigMetadata, MultisigMetadataBuilder } from './multisig.js'; +import { NullMetadata, NullMetadataBuilder } from './null.js'; +import { RoutingMetadata, RoutingMetadataBuilder } from './routing.js'; + +export type StructuredMetadata = + | NullMetadata + | MultisigMetadata + | AggregationMetadata + | RoutingMetadata; + +export interface MetadataContext< + IsmContext = DerivedIsmConfig, + HookContext = DerivedHookConfig, +> { + message: DispatchedMessage; + dispatchTx: TransactionReceipt; + ism: IsmContext; + hook: HookContext; +} + +export interface MetadataBuilder { + build(context: MetadataContext): Promise; +} + +export class BaseMetadataBuilder implements MetadataBuilder { + public nullMetadataBuilder: NullMetadataBuilder; + public multisigMetadataBuilder: MultisigMetadataBuilder; + public aggregationMetadataBuilder: AggregationMetadataBuilder; + public routingMetadataBuilder: RoutingMetadataBuilder; + + public multiProvider: MultiProvider; + protected logger = rootLogger.child({ module: 'BaseMetadataBuilder' }); + + constructor(core: HyperlaneCore) { + this.multisigMetadataBuilder = new MultisigMetadataBuilder(core); + this.aggregationMetadataBuilder = new AggregationMetadataBuilder(this); + this.routingMetadataBuilder = new RoutingMetadataBuilder(this); + this.nullMetadataBuilder = new NullMetadataBuilder(core.multiProvider); + this.multiProvider = core.multiProvider; + } + + // assumes that all post dispatch hooks are included in dispatchTx logs + async build(context: MetadataContext, maxDepth = 10): Promise { + this.logger.debug( + { context, maxDepth }, + `Building ${context.ism.type} metadata`, + ); + assert(maxDepth > 0, 'Max depth reached'); + + const { ism, hook } = context; + switch (ism.type) { + case IsmType.TRUSTED_RELAYER: + case IsmType.TEST_ISM: + case IsmType.OP_STACK: + case IsmType.PAUSABLE: + return this.nullMetadataBuilder.build({ ...context, ism }); + + case IsmType.MERKLE_ROOT_MULTISIG: + case IsmType.MESSAGE_ID_MULTISIG: + const merkleTreeHook = deepFind( + hook, + (v): v is WithAddress => + v.type === HookType.MERKLE_TREE && !!v.address, + ); + assert(merkleTreeHook, 'Merkle tree hook context not found'); + return this.multisigMetadataBuilder.build({ + ...context, + ism, + hook: merkleTreeHook, + }); + + case IsmType.ROUTING: + return this.routingMetadataBuilder.build( + { + ...context, + ism, + }, + maxDepth, + ); + + case IsmType.AGGREGATION: + return this.aggregationMetadataBuilder.build( + { ...context, ism }, + maxDepth, + ); + + default: + throw new Error(`Unsupported ISM type: ${ism.type}`); + } + } + + static decode( + metadata: string, + context: MetadataContext, + ): StructuredMetadata { + const { ism } = context; + switch (ism.type) { + case IsmType.TRUSTED_RELAYER: + return NullMetadataBuilder.decode(ism); + + case IsmType.MERKLE_ROOT_MULTISIG: + case IsmType.MESSAGE_ID_MULTISIG: + return MultisigMetadataBuilder.decode(metadata, ism.type); + + case IsmType.AGGREGATION: + return AggregationMetadataBuilder.decode(metadata, { ...context, ism }); + + case IsmType.ROUTING: + return RoutingMetadataBuilder.decode(metadata, { ...context, ism }); + + default: + throw new Error(`Unsupported ISM type: ${ism.type}`); + } + } +} diff --git a/typescript/sdk/src/ism/metadata/multisig.test.ts b/typescript/sdk/src/ism/metadata/multisig.test.ts index 1afca2bdb..64445f82a 100644 --- a/typescript/sdk/src/ism/metadata/multisig.test.ts +++ b/typescript/sdk/src/ism/metadata/multisig.test.ts @@ -1,19 +1,19 @@ import { expect } from 'chai'; -import { readFileSync, readdirSync } from 'fs'; +import { existsSync, readFileSync, readdirSync } from 'fs'; import { SignatureLike } from '@hyperlane-xyz/utils'; -import { ModuleType } from '../types.js'; +import { IsmType, ModuleType } from '../types.js'; import { MultisigMetadata, MultisigMetadataBuilder } from './multisig.js'; import { Fixture } from './types.test.js'; const path = '../../solidity/fixtures/multisig'; -const files = readdirSync(path); +const files = existsSync(path) ? readdirSync(path) : []; const fixtures: Fixture[] = files .map((f) => JSON.parse(readFileSync(`${path}/${f}`, 'utf8'))) .map((contents) => { - const type = contents.type as MultisigMetadata['type']; + const type = contents.type as ModuleType; const { dummy: _dummy, ...signatureValues } = contents.signatures; const signatures = Object.values(signatureValues); @@ -23,7 +23,7 @@ const fixtures: Fixture[] = files const { dummy: _dummy, ...branchValues } = contents.prefix.proof; const branch = Object.values(branchValues); decoded = { - type, + type: IsmType.MERKLE_ROOT_MULTISIG, proof: { branch, leaf: contents.prefix.id, @@ -38,7 +38,7 @@ const fixtures: Fixture[] = files }; } else { decoded = { - type, + type: IsmType.MESSAGE_ID_MULTISIG, checkpoint: { root: contents.prefix.root, index: contents.prefix.signedIndex, diff --git a/typescript/sdk/src/ism/metadata/multisig.ts b/typescript/sdk/src/ism/metadata/multisig.ts index f27e7114f..afb6539b0 100644 --- a/typescript/sdk/src/ism/metadata/multisig.ts +++ b/typescript/sdk/src/ism/metadata/multisig.ts @@ -1,39 +1,217 @@ import { joinSignature, splitSignature } from 'ethers/lib/utils.js'; +import { MerkleTreeHook__factory } from '@hyperlane-xyz/core'; import { + Address, Checkpoint, MerkleProof, + S3CheckpointWithId, SignatureLike, + WithAddress, assert, + bytes32ToAddress, chunk, ensure0x, + eqAddress, + eqAddressEvm, fromHexString, + rootLogger, strip0x, toHexString, } from '@hyperlane-xyz/utils'; -import { ModuleType } from '../types.js'; +import { S3Validator } from '../../aws/validator.js'; +import { HyperlaneCore } from '../../core/HyperlaneCore.js'; +import { MerkleTreeHookConfig } from '../../hook/types.js'; +import { ChainName } from '../../types.js'; +import { IsmType, MultisigIsmConfig } from '../types.js'; + +import { MetadataBuilder, MetadataContext } from './builder.js'; interface MessageIdMultisigMetadata { - type: ModuleType.MESSAGE_ID_MULTISIG; + type: IsmType.MESSAGE_ID_MULTISIG; signatures: SignatureLike[]; checkpoint: Omit; } interface MerkleRootMultisigMetadata extends Omit { - type: ModuleType.MERKLE_ROOT_MULTISIG; + type: IsmType.MERKLE_ROOT_MULTISIG; proof: MerkleProof; } +const MerkleTreeInterface = MerkleTreeHook__factory.createInterface(); + const SIGNATURE_LENGTH = 65; export type MultisigMetadata = | MessageIdMultisigMetadata | MerkleRootMultisigMetadata; -export class MultisigMetadataBuilder { - static encodeSimplePrefix(metadata: MessageIdMultisigMetadata): string { +export class MultisigMetadataBuilder implements MetadataBuilder { + protected validatorCache: Record> = {}; + + constructor( + protected readonly core: HyperlaneCore, + protected readonly logger = rootLogger.child({ + module: 'MultisigMetadataBuilder', + }), + ) {} + + protected async s3Validators( + originChain: ChainName, + validators: string[], + ): Promise { + this.validatorCache[originChain] ??= {}; + const toFetch = validators.filter( + (v) => !(v in this.validatorCache[originChain]), + ); + + if (toFetch.length > 0) { + const validatorAnnounce = + this.core.getContracts(originChain).validatorAnnounce; + const storageLocations = + await validatorAnnounce.getAnnouncedStorageLocations(toFetch); + + this.logger.debug({ storageLocations }, 'Fetched storage locations'); + + const s3Validators = await Promise.all( + storageLocations.map((locations) => { + const latestLocation = locations.slice(-1)[0]; + return S3Validator.fromStorageLocation(latestLocation); + }), + ); + + this.logger.debug({ s3Validators }, 'Fetched validators'); + + toFetch.forEach((validator, index) => { + this.validatorCache[originChain][validator] = s3Validators[index]; + }); + } + + return validators.map((v) => this.validatorCache[originChain][v]); + } + + async getS3Checkpoints( + validators: Address[], + match: { + origin: number; + merkleTree: Address; + messageId: string; + index: number; + }, + ): Promise { + this.logger.debug({ match, validators }, 'Fetching checkpoints'); + + const originChain = this.core.multiProvider.getChainName(match.origin); + const s3Validators = await this.s3Validators(originChain, validators); + + const results = await Promise.allSettled( + s3Validators.map((v) => v.getCheckpoint(match.index)), + ); + results + .filter((r) => r.status === 'rejected') + .forEach((r) => { + this.logger.error({ error: r }, 'Failed to fetch checkpoint'); + }); + const checkpoints = results + .filter( + (result): result is PromiseFulfilledResult => + result.status === 'fulfilled' && result.value !== undefined, + ) + .map((result) => result.value); + + this.logger.debug({ checkpoints }, 'Fetched checkpoints'); + + if (checkpoints.length < validators.length) { + this.logger.debug( + { checkpoints, validators, match }, + `Found ${checkpoints.length} checkpoints out of ${validators.length} validators`, + ); + } + + const matchingCheckpoints = checkpoints.filter( + ({ value }) => + eqAddress( + bytes32ToAddress(value.checkpoint.merkle_tree_hook_address), + match.merkleTree, + ) && + value.message_id === match.messageId && + value.checkpoint.index === match.index && + value.checkpoint.mailbox_domain === match.origin, + ); + + if (matchingCheckpoints.length !== checkpoints.length) { + this.logger.warn( + { matchingCheckpoints, checkpoints, match }, + 'Mismatched checkpoints', + ); + } + + return matchingCheckpoints; + } + + async build( + context: MetadataContext< + WithAddress, + WithAddress + >, + ): Promise { + assert( + context.ism.type === IsmType.MESSAGE_ID_MULTISIG, + 'Merkle proofs are not yet supported', + ); + + const merkleTree = context.hook.address; + + const matchingInsertion = context.dispatchTx.logs + .filter((log) => eqAddressEvm(log.address, merkleTree)) + .map((log) => MerkleTreeInterface.parseLog(log)) + .find((event) => event.args.messageId === context.message.id); + + assert( + matchingInsertion, + `No merkle tree insertion of ${context.message.id} to ${merkleTree} found in dispatch tx`, + ); + this.logger.debug({ matchingInsertion }, 'Found matching insertion event'); + + const checkpoints = await this.getS3Checkpoints(context.ism.validators, { + origin: context.message.parsed.origin, + messageId: context.message.id, + merkleTree, + index: matchingInsertion.args.index, + }); + assert( + checkpoints.length >= context.ism.threshold, + `Only ${checkpoints.length} of ${context.ism.threshold} required checkpoints found`, + ); + + this.logger.debug( + { checkpoints }, + `Found ${checkpoints.length} checkpoints for message ${context.message.id}`, + ); + + const signatures = checkpoints + .map((checkpoint) => checkpoint.signature) + .slice(0, context.ism.threshold); + + this.logger.debug( + { signatures, ism: context.ism }, + `Taking ${signatures.length} (threshold) signatures for message ${context.message.id}`, + ); + + const metadata: MessageIdMultisigMetadata = { + type: IsmType.MESSAGE_ID_MULTISIG, + checkpoint: checkpoints[0].value.checkpoint, + signatures, + }; + + return MultisigMetadataBuilder.encode(metadata); + } + + protected static encodeSimplePrefix( + metadata: MessageIdMultisigMetadata, + ): string { const checkpoint = metadata.checkpoint; const buf = Buffer.alloc(68); buf.write(strip0x(checkpoint.merkle_tree_hook_address), 0, 32, 'hex'); @@ -54,7 +232,7 @@ export class MultisigMetadataBuilder { }; return { signatureOffset: 68, - type: ModuleType.MESSAGE_ID_MULTISIG, + type: IsmType.MESSAGE_ID_MULTISIG, checkpoint, }; } @@ -93,7 +271,7 @@ export class MultisigMetadataBuilder { }; return { signatureOffset: 1096, - type: ModuleType.MERKLE_ROOT_MULTISIG, + type: IsmType.MERKLE_ROOT_MULTISIG, checkpoint, proof, }; @@ -101,7 +279,7 @@ export class MultisigMetadataBuilder { static encode(metadata: MultisigMetadata): string { let encoded = - metadata.type === ModuleType.MESSAGE_ID_MULTISIG + metadata.type === IsmType.MESSAGE_ID_MULTISIG ? this.encodeSimplePrefix(metadata) : this.encodeProofPrefix(metadata); @@ -131,10 +309,10 @@ export class MultisigMetadataBuilder { static decode( metadata: string, - type: ModuleType.MERKLE_ROOT_MULTISIG | ModuleType.MESSAGE_ID_MULTISIG, + type: IsmType.MERKLE_ROOT_MULTISIG | IsmType.MESSAGE_ID_MULTISIG, ): MultisigMetadata { const prefix: any = - type === ModuleType.MERKLE_ROOT_MULTISIG + type === IsmType.MERKLE_ROOT_MULTISIG ? this.decodeProofPrefix(metadata) : this.decodeSimplePrefix(metadata); diff --git a/typescript/sdk/src/ism/metadata/null.ts b/typescript/sdk/src/ism/metadata/null.ts new file mode 100644 index 000000000..ed66277ce --- /dev/null +++ b/typescript/sdk/src/ism/metadata/null.ts @@ -0,0 +1,35 @@ +import { WithAddress, assert, eqAddress } from '@hyperlane-xyz/utils'; + +import { MultiProvider } from '../../providers/MultiProvider.js'; +import { IsmType, NullIsmConfig } from '../types.js'; + +import { MetadataBuilder, MetadataContext } from './builder.js'; + +export const NULL_METADATA = '0x'; + +export type NullMetadata = { + type: NullIsmConfig['type']; +}; + +export class NullMetadataBuilder implements MetadataBuilder { + constructor(protected multiProvider: MultiProvider) {} + + async build( + context: MetadataContext>, + ): Promise { + if (context.ism.type === IsmType.TRUSTED_RELAYER) { + const destinationSigner = await this.multiProvider.getSignerAddress( + context.message.parsed.destination, + ); + assert( + eqAddress(destinationSigner, context.ism.relayer), + `Destination signer ${destinationSigner} does not match trusted relayer ${context.ism.relayer}`, + ); + } + return NULL_METADATA; + } + + static decode(ism: NullIsmConfig): NullMetadata { + return { ...ism }; + } +} diff --git a/typescript/sdk/src/ism/metadata/routing.ts b/typescript/sdk/src/ism/metadata/routing.ts new file mode 100644 index 000000000..a3adb819d --- /dev/null +++ b/typescript/sdk/src/ism/metadata/routing.ts @@ -0,0 +1,58 @@ +import { WithAddress, assert } from '@hyperlane-xyz/utils'; + +import { ChainName } from '../../types.js'; +import { DerivedIsmConfig } from '../EvmIsmReader.js'; +import { IsmType, RoutingIsmConfig } from '../types.js'; + +import { + BaseMetadataBuilder, + MetadataBuilder, + MetadataContext, + StructuredMetadata, +} from './builder.js'; + +export type RoutingMetadata = { + type: IsmType.ROUTING; + origin: ChainName; + metadata: T; +}; + +export class RoutingMetadataBuilder implements MetadataBuilder { + constructor(protected baseMetadataBuilder: BaseMetadataBuilder) {} + + public async build( + context: MetadataContext>, + maxDepth = 10, + ): Promise { + const originChain = this.baseMetadataBuilder.multiProvider.getChainName( + context.message.parsed.origin, + ); + const originContext = { + ...context, + ism: context.ism.domains[originChain] as DerivedIsmConfig, + }; + return this.baseMetadataBuilder.build(originContext, maxDepth - 1); + } + + static decode( + metadata: string, + context: MetadataContext>, + ): RoutingMetadata { + // TODO: this is a naive implementation, we should support domain ID keys + assert(context.message.parsed.originChain, 'originChain is required'); + const ism = context.ism.domains[context.message.parsed.originChain]; + const originMetadata = + typeof ism === 'string' + ? metadata + : BaseMetadataBuilder.decode(metadata, { + ...context, + ism: ism as DerivedIsmConfig, + }); + + return { + type: IsmType.ROUTING, + origin: context.message.parsed.originChain, + metadata: originMetadata, + }; + } +} diff --git a/typescript/sdk/src/ism/schemas.test.ts b/typescript/sdk/src/ism/schemas.test.ts index db4410903..7605382c2 100644 --- a/typescript/sdk/src/ism/schemas.test.ts +++ b/typescript/sdk/src/ism/schemas.test.ts @@ -1,8 +1,7 @@ import { expect } from 'chai'; import { ethers } from 'ethers'; -import { AggregationIsmConfigSchema } from '@hyperlane-xyz/sdk'; - +import { AggregationIsmConfigSchema } from './schemas.js'; import { IsmType } from './types.js'; const SOME_ADDRESS = ethers.Wallet.createRandom().address; diff --git a/typescript/sdk/src/ism/schemas.ts b/typescript/sdk/src/ism/schemas.ts index f07df3f25..c449e0e42 100644 --- a/typescript/sdk/src/ism/schemas.ts +++ b/typescript/sdk/src/ism/schemas.ts @@ -1,7 +1,7 @@ import { z } from 'zod'; import { OwnableConfigSchema } from '../deploy/schemas.js'; -import { ZHash } from '../index.js'; +import { ZHash } from '../metadata/customZodTypes.js'; import { AggregationIsmConfig, IsmConfig, IsmType } from './types.js'; diff --git a/typescript/sdk/src/ism/types.ts b/typescript/sdk/src/ism/types.ts index 0a08676e9..89892662d 100644 --- a/typescript/sdk/src/ism/types.ts +++ b/typescript/sdk/src/ism/types.ts @@ -100,15 +100,18 @@ export type TrustedRelayerIsmConfig = { relayer: Address; }; +export type NullIsmConfig = + | PausableIsmConfig + | TestIsmConfig + | OpStackIsmConfig + | TrustedRelayerIsmConfig; + export type IsmConfig = | Address + | NullIsmConfig | RoutingIsmConfig | MultisigIsmConfig - | AggregationIsmConfig - | OpStackIsmConfig - | TestIsmConfig - | PausableIsmConfig - | TrustedRelayerIsmConfig; + | AggregationIsmConfig; export type DeployedIsmType = { [IsmType.ROUTING]: IRoutingIsm; diff --git a/typescript/sdk/src/metadata/agentConfig.ts b/typescript/sdk/src/metadata/agentConfig.ts index 3c16651f6..0885a3e4a 100644 --- a/typescript/sdk/src/metadata/agentConfig.ts +++ b/typescript/sdk/src/metadata/agentConfig.ts @@ -106,7 +106,7 @@ const AgentCosmosChainMetadataSchema = z.object({ amount: z .string() .regex(/^(\d*[.])?\d+$/) - .describe('The the gas price, in denom, to pay for each unit of gas'), + .describe('The gas price, in denom, to pay for each unit of gas'), }), contractAddressBytes: z .number() diff --git a/typescript/sdk/src/middleware/account/schemas.ts b/typescript/sdk/src/middleware/account/schemas.ts index fd0e91d2c..5b95b13d1 100644 --- a/typescript/sdk/src/middleware/account/schemas.ts +++ b/typescript/sdk/src/middleware/account/schemas.ts @@ -1,7 +1,6 @@ import { z } from 'zod'; -import { ZHash } from '../../index.js'; -import { ZChainName } from '../../metadata/customZodTypes.js'; +import { ZChainName, ZHash } from '../../metadata/customZodTypes.js'; export const AccountConfigSchema = z.object({ origin: ZChainName, diff --git a/typescript/sdk/src/router/GasRouterDeployer.ts b/typescript/sdk/src/router/GasRouterDeployer.ts index 8c7d0a52e..2c929123a 100644 --- a/typescript/sdk/src/router/GasRouterDeployer.ts +++ b/typescript/sdk/src/router/GasRouterDeployer.ts @@ -4,15 +4,18 @@ import { Address } from '@hyperlane-xyz/utils'; import { HyperlaneContracts, HyperlaneContractsMap, + HyperlaneFactories, } from '../contracts/types.js'; import { ChainMap } from '../types.js'; import { ProxiedRouterDeployer } from './ProxiedRouterDeployer.js'; -import { GasRouterConfig, ProxiedFactories } from './types.js'; +import { GasRouterConfig } from './types.js'; + +const DEFAULT_GAS_OVERHEAD = 100_000; export abstract class GasRouterDeployer< Config extends GasRouterConfig, - Factories extends ProxiedFactories, + Factories extends HyperlaneFactories, > extends ProxiedRouterDeployer { abstract router(contracts: HyperlaneContracts): GasRouter; @@ -37,7 +40,7 @@ export abstract class GasRouterDeployer< const remoteConfigs = remoteDomains .map((domain, i) => ({ domain, - gas: configMap[remoteChains[i]].gas, + gas: configMap[remoteChains[i]].gas ?? DEFAULT_GAS_OVERHEAD, })) .filter(({ gas }, index) => !currentConfigs[index].eq(gas)); if (remoteConfigs.length == 0) { diff --git a/typescript/sdk/src/router/ProxiedRouterDeployer.ts b/typescript/sdk/src/router/ProxiedRouterDeployer.ts index f8a560804..335a421e2 100644 --- a/typescript/sdk/src/router/ProxiedRouterDeployer.ts +++ b/typescript/sdk/src/router/ProxiedRouterDeployer.ts @@ -7,18 +7,41 @@ import { } from '@hyperlane-xyz/core'; import { eqAddress } from '@hyperlane-xyz/utils'; -import { HyperlaneContracts } from '../contracts/types.js'; +import { HyperlaneContracts, HyperlaneFactories } from '../contracts/types.js'; +import { DeployerOptions } from '../deploy/HyperlaneDeployer.js'; import { resolveOrDeployAccountOwner } from '../deploy/types.js'; +import { MultiProvider } from '../providers/MultiProvider.js'; import { ChainName } from '../types.js'; import { HyperlaneRouterDeployer } from './HyperlaneRouterDeployer.js'; -import { ProxiedFactories, ProxiedRouterConfig } from './types.js'; +import { + ProxiedFactories, + ProxiedRouterConfig, + proxiedFactories, +} from './types.js'; export abstract class ProxiedRouterDeployer< Config extends ProxiedRouterConfig, - Factories extends ProxiedFactories, -> extends HyperlaneRouterDeployer { - abstract router(contracts: HyperlaneContracts): Router; + Factories extends HyperlaneFactories, +> extends HyperlaneRouterDeployer { + constructor( + multiProvider: MultiProvider, + factories: Factories, + options?: DeployerOptions, + ) { + super( + multiProvider, + { + ...factories, + ...proxiedFactories, + }, + options, + ); + } + + abstract router( + contracts: HyperlaneContracts, + ): Router; /** * Returns the contract name @@ -59,7 +82,7 @@ export abstract class ProxiedRouterDeployer< async deployContracts( chain: ChainName, config: Config, - ): Promise> { + ): Promise> { const proxyAdmin = await this.deployContractFromFactory( chain, this.factories.proxyAdmin, @@ -112,6 +135,6 @@ export abstract class ProxiedRouterDeployer< [this.routerContractKey(config)]: proxiedRouter, proxyAdmin, timelockController, - } as HyperlaneContracts; + } as HyperlaneContracts; } } diff --git a/typescript/sdk/src/router/schemas.ts b/typescript/sdk/src/router/schemas.ts index e186c4e1c..6c6ffa031 100644 --- a/typescript/sdk/src/router/schemas.ts +++ b/typescript/sdk/src/router/schemas.ts @@ -1,21 +1,23 @@ import { z } from 'zod'; import { OwnableConfigSchema } from '../deploy/schemas.js'; -import { ZHash } from '../index.js'; import { IsmConfigSchema } from '../ism/schemas.js'; +import { ZHash } from '../metadata/customZodTypes.js'; -export const ForeignDeploymentConfigSchema = z.object({ - foreignDeployment: z.string().optional(), -}); - -export const MailboxClientConfigSchema = z.object({ +export const MailboxClientConfigSchema = OwnableConfigSchema.extend({ mailbox: ZHash, hook: ZHash.optional(), interchainSecurityModule: IsmConfigSchema.optional(), }); -export const routerConfigSchema = MailboxClientConfigSchema.merge( - OwnableConfigSchema, -) - .merge(ForeignDeploymentConfigSchema) - .deepPartial(); +export const ForeignDeploymentConfigSchema = z.object({ + foreignDeployment: z.string().optional(), +}); + +export const RouterConfigSchema = MailboxClientConfigSchema.merge( + ForeignDeploymentConfigSchema, +); + +export const GasRouterConfigSchema = RouterConfigSchema.extend({ + gas: z.number().optional(), +}); diff --git a/typescript/sdk/src/router/types.ts b/typescript/sdk/src/router/types.ts index f1c7ab448..1d4202e05 100644 --- a/typescript/sdk/src/router/types.ts +++ b/typescript/sdk/src/router/types.ts @@ -6,37 +6,27 @@ import { Router, TimelockController__factory, } from '@hyperlane-xyz/core'; +import { Address } from '@hyperlane-xyz/utils'; -import type { Address } from '../../../utils/dist/index.js'; import { HyperlaneFactories } from '../contracts/types.js'; import { UpgradeConfig } from '../deploy/proxy.js'; -import { CheckerViolation, OwnableConfig } from '../deploy/types.js'; +import { CheckerViolation } from '../deploy/types.js'; import { - ForeignDeploymentConfigSchema, + GasRouterConfigSchema, MailboxClientConfigSchema, + RouterConfigSchema, } from './schemas.js'; export type RouterAddress = { router: Address; }; -export type ForeignDeploymentConfig = z.infer< - typeof ForeignDeploymentConfigSchema ->; - -export type RouterConfig = MailboxClientConfig & - OwnableConfig & - ForeignDeploymentConfig; +export type MailboxClientConfig = z.infer; +export type RouterConfig = z.infer; +export type GasRouterConfig = z.infer; export type ProxiedRouterConfig = RouterConfig & Partial; - -export type GasConfig = { - gas: number; -}; - -export type GasRouterConfig = RouterConfig & GasConfig; - export type ProxiedFactories = HyperlaneFactories & { proxyAdmin: ProxyAdmin__factory; timelockController: TimelockController__factory; @@ -47,10 +37,6 @@ export const proxiedFactories: ProxiedFactories = { timelockController: new TimelockController__factory(), }; -// TODO: merge with kunal's hook deployer - -export type MailboxClientConfig = z.infer; - export enum ClientViolationType { InterchainSecurityModule = 'ClientIsm', Mailbox = 'ClientMailbox', diff --git a/typescript/sdk/src/test/testUtils.ts b/typescript/sdk/src/test/testUtils.ts index 7dbd38424..fc498469a 100644 --- a/typescript/sdk/src/test/testUtils.ts +++ b/typescript/sdk/src/test/testUtils.ts @@ -12,10 +12,6 @@ import { IsmType } from '../ism/types.js'; import { RouterConfig } from '../router/types.js'; import { ChainMap, ChainName } from '../types.js'; -export function randomInt(max: number, min = 0): number { - return Math.floor(Math.random() * (max - min)) + min; -} - export function randomAddress(): Address { return ethers.utils.hexlify(ethers.utils.randomBytes(20)); } diff --git a/typescript/sdk/src/token/EvmERC20WarpRouteReader.ts b/typescript/sdk/src/token/EvmERC20WarpRouteReader.ts index 0699fe5c4..d62285705 100644 --- a/typescript/sdk/src/token/EvmERC20WarpRouteReader.ts +++ b/typescript/sdk/src/token/EvmERC20WarpRouteReader.ts @@ -3,24 +3,24 @@ import { ethers, providers } from 'ethers'; import { ERC20__factory, HypERC20Collateral__factory, + MailboxClient__factory, } from '@hyperlane-xyz/core'; -import { ERC20Metadata, ERC20RouterConfig } from '@hyperlane-xyz/sdk'; -import { Address } from '@hyperlane-xyz/utils'; +import { Address, eqAddress } from '@hyperlane-xyz/utils'; import { DEFAULT_CONTRACT_READ_CONCURRENCY } from '../consts/concurrency.js'; import { EvmHookReader } from '../hook/EvmHookReader.js'; import { EvmIsmReader } from '../ism/EvmIsmReader.js'; import { MultiProvider } from '../providers/MultiProvider.js'; +import { MailboxClientConfig } from '../router/types.js'; import { ChainName } from '../types.js'; -type WarpRouteBaseMetadata = Record< - 'mailbox' | 'owner' | 'token' | 'hook' | 'interchainSecurityModule', - string ->; +import { TokenType } from './config.js'; +import { TokenRouterConfig } from './schemas.js'; +import { TokenMetadata } from './types.js'; -type DerivedERC20WarpRouteConfig = Omit; +const { AddressZero } = ethers.constants; -export class EvmERC20WarpRouteReader { +export class EvmWarpRouteReader { provider: providers.Provider; evmHookReader: EvmHookReader; evmIsmReader: EvmIsmReader; @@ -44,34 +44,32 @@ export class EvmERC20WarpRouteReader { */ async deriveWarpRouteConfig( address: Address, - ): Promise { - const fetchedBaseMetadata = await this.fetchBaseMetadata(address); - const fetchedTokenMetadata = await this.fetchTokenMetadata( - fetchedBaseMetadata.token, - ); - - const results: DerivedERC20WarpRouteConfig = { - ...fetchedBaseMetadata, - ...fetchedTokenMetadata, - }; + type = TokenType.collateral, + ): Promise { + const mailboxClientConfig = await this.fetchMailboxClientConfig(address); - if ( - fetchedBaseMetadata.interchainSecurityModule !== - ethers.constants.AddressZero - ) { - results.interchainSecurityModule = - await this.evmIsmReader.deriveIsmConfig( - fetchedBaseMetadata.interchainSecurityModule, - ); + let token: Address; + switch (type) { + case TokenType.collateral: + token = await HypERC20Collateral__factory.connect( + address, + this.provider, + ).wrappedToken(); + break; + case TokenType.synthetic: + token = address; + break; + default: + throw new Error(`Invalid token type: ${type}`); } - // @todo add after https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/3667 is fixed - // if (fetchedBaseMetadata.hook !== ethers.constants.AddressZero) { - // results.hook = await this.evmHookReader.deriveHookConfig( - // fetchedBaseMetadata.hook, - // ); - // } + const fetchedTokenMetadata = await this.fetchTokenMetadata(token); - return results; + return { + type, + token: TokenType.collateral === type ? token : undefined, + ...mailboxClientConfig, + ...fetchedTokenMetadata, + } as TokenRouterConfig; } /** @@ -80,28 +78,31 @@ export class EvmERC20WarpRouteReader { * @param routerAddress - The address of the Warp Route contract. * @returns The base metadata for the Warp Route contract, including the mailbox, owner, wrapped token address, hook, and interchain security module. */ - async fetchBaseMetadata( + async fetchMailboxClientConfig( routerAddress: Address, - ): Promise { - const warpRoute = HypERC20Collateral__factory.connect( + ): Promise { + const warpRoute = MailboxClient__factory.connect( routerAddress, this.provider, ); - const [mailbox, owner, token, hook, interchainSecurityModule] = - await Promise.all([ - warpRoute.mailbox(), - warpRoute.owner(), - warpRoute.wrappedToken(), - warpRoute.hook(), - warpRoute.interchainSecurityModule(), - ]); + const [mailbox, owner, hook, ism] = await Promise.all([ + warpRoute.mailbox(), + warpRoute.owner(), + warpRoute.hook(), + warpRoute.interchainSecurityModule(), + ]); + + const derivedIsm = eqAddress(ism, AddressZero) + ? undefined + : await this.evmIsmReader.deriveIsmConfig(ism); + // TODO: add after https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/3667 is fixed + const derivedHook = eqAddress(hook, AddressZero) ? undefined : hook; return { mailbox, owner, - token, - hook, - interchainSecurityModule, + hook: derivedHook, + interchainSecurityModule: derivedIsm, }; } @@ -111,7 +112,7 @@ export class EvmERC20WarpRouteReader { * @param tokenAddress - The address of the token. * @returns A partial ERC20 metadata object containing the token name, symbol, total supply, and decimals. */ - async fetchTokenMetadata(tokenAddress: Address): Promise { + async fetchTokenMetadata(tokenAddress: Address): Promise { const erc20 = ERC20__factory.connect(tokenAddress, this.provider); const [name, symbol, totalSupply, decimals] = await Promise.all([ erc20.name(), @@ -120,6 +121,6 @@ export class EvmERC20WarpRouteReader { erc20.decimals(), ]); - return { name, symbol, totalSupply, decimals }; + return { name, symbol, totalSupply: totalSupply.toString(), decimals }; } } diff --git a/typescript/sdk/src/token/Token.test.ts b/typescript/sdk/src/token/Token.test.ts index 3a1e6414d..9cd97c919 100644 --- a/typescript/sdk/src/token/Token.test.ts +++ b/typescript/sdk/src/token/Token.test.ts @@ -47,33 +47,6 @@ const STANDARD_TO_TOKEN: Record = { symbol: 'USDC', name: 'USDC', }, - [TokenStandard.EvmHypXERC20Collateral]: { - chainName: TestChainName.test3, - standard: TokenStandard.EvmHypXERC20Collateral, - addressOrDenom: '0x31b5234A896FbC4b3e2F7237592D054716762131', - collateralAddressOrDenom: '0x64544969ed7ebf5f083679233325356ebe738930', - decimals: 18, - symbol: 'USDC', - name: 'USDC', - }, - [TokenStandard.EvmHypFiatCollateral]: { - chainName: TestChainName.test3, - standard: TokenStandard.EvmHypXERC20Collateral, - addressOrDenom: '0x31b5234A896FbC4b3e2F7237592D054716762131', - collateralAddressOrDenom: '0x64544969ed7ebf5f083679233325356ebe738930', - decimals: 18, - symbol: 'USDC', - name: 'USDC', - }, - [TokenStandard.EvmHypCollateralVault]: { - chainName: TestChainName.test3, - standard: TokenStandard.EvmHypCollateral, - addressOrDenom: '0x31b5234A896FbC4b3e2F7237592D054716762131', - collateralAddressOrDenom: '0x64544969ed7ebf5f083679233325356ebe738930', - decimals: 18, - symbol: 'USDC', - name: 'USDC', - }, [TokenStandard.EvmHypSynthetic]: { chainName: TestChainName.test2, standard: TokenStandard.EvmHypSynthetic, diff --git a/typescript/sdk/src/token/Token.ts b/typescript/sdk/src/token/Token.ts index 5c0e0af64..7ddc870e5 100644 --- a/typescript/sdk/src/token/Token.ts +++ b/typescript/sdk/src/token/Token.ts @@ -205,12 +205,7 @@ export class Token implements IToken { return new EvmHypNativeAdapter(chainName, multiProvider, { token: addressOrDenom, }); - } else if ( - standard === TokenStandard.EvmHypCollateral || - standard === TokenStandard.EvmHypCollateralVault || - standard === TokenStandard.EvmHypXERC20Collateral || - standard === TokenStandard.EvmHypFiatCollateral - ) { + } else if (standard === TokenStandard.EvmHypCollateral) { return new EvmHypCollateralAdapter(chainName, multiProvider, { token: addressOrDenom, }); diff --git a/typescript/sdk/src/token/TokenStandard.ts b/typescript/sdk/src/token/TokenStandard.ts index d1394d49c..8bdd0defc 100644 --- a/typescript/sdk/src/token/TokenStandard.ts +++ b/typescript/sdk/src/token/TokenStandard.ts @@ -14,9 +14,6 @@ export enum TokenStandard { EvmNative = 'EvmNative', EvmHypNative = 'EvmHypNative', EvmHypCollateral = 'EvmHypCollateral', - EvmHypXERC20Collateral = 'EvmHypXERC20Collateral', - EvmHypFiatCollateral = 'EvmHypFiatCollateral', - EvmHypCollateralVault = 'EvmHypCollateralVault', EvmHypSynthetic = 'EvmHypSynthetic', // Sealevel (Solana) @@ -50,10 +47,7 @@ export const TOKEN_STANDARD_TO_PROTOCOL: Record = { EvmNative: ProtocolType.Ethereum, EvmHypNative: ProtocolType.Ethereum, EvmHypCollateral: ProtocolType.Ethereum, - EvmHypCollateralVault: ProtocolType.Ethereum, EvmHypSynthetic: ProtocolType.Ethereum, - EvmHypXERC20Collateral: ProtocolType.Ethereum, - EvmHypFiatCollateral: ProtocolType.Ethereum, // Sealevel (Solana) SealevelSpl: ProtocolType.Sealevel, @@ -96,8 +90,6 @@ export const TOKEN_NFT_STANDARDS = [ export const TOKEN_COLLATERALIZED_STANDARDS = [ TokenStandard.EvmHypCollateral, TokenStandard.EvmHypNative, - TokenStandard.EvmHypXERC20Collateral, - TokenStandard.EvmHypFiatCollateral, TokenStandard.SealevelHypCollateral, TokenStandard.SealevelHypNative, TokenStandard.CwHypCollateral, @@ -107,8 +99,6 @@ export const TOKEN_COLLATERALIZED_STANDARDS = [ export const TOKEN_HYP_STANDARDS = [ TokenStandard.EvmHypNative, TokenStandard.EvmHypCollateral, - TokenStandard.EvmHypXERC20Collateral, - TokenStandard.EvmHypFiatCollateral, TokenStandard.EvmHypSynthetic, TokenStandard.SealevelHypNative, TokenStandard.SealevelHypCollateral, @@ -137,9 +127,10 @@ export const TOKEN_COSMWASM_STANDARDS = [ export const TOKEN_TYPE_TO_STANDARD: Record = { [TokenType.native]: TokenStandard.EvmHypNative, [TokenType.collateral]: TokenStandard.EvmHypCollateral, - [TokenType.collateralFiat]: TokenStandard.EvmHypFiatCollateral, - [TokenType.collateralXERC20]: TokenStandard.EvmHypXERC20Collateral, - [TokenType.collateralVault]: TokenStandard.EvmHypCollateralVault, + [TokenType.collateralFiat]: TokenStandard.EvmHypCollateral, + [TokenType.XERC20]: TokenStandard.EvmHypCollateral, + [TokenType.XERC20Lockbox]: TokenStandard.EvmHypCollateral, + [TokenType.collateralVault]: TokenStandard.EvmHypCollateral, [TokenType.collateralUri]: TokenStandard.EvmHypCollateral, [TokenType.fastCollateral]: TokenStandard.EvmHypCollateral, [TokenType.synthetic]: TokenStandard.EvmHypSynthetic, diff --git a/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.ts b/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.ts index 9ee5fba38..e1b476520 100644 --- a/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.ts +++ b/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.ts @@ -30,7 +30,7 @@ import { } from '../../cw-types/WarpCw20.types.js'; import { MultiProtocolProvider } from '../../providers/MultiProtocolProvider.js'; import { ChainName } from '../../types.js'; -import { ERC20Metadata } from '../config.js'; +import { TokenMetadata } from '../types.js'; import { IHypTokenAdapter, @@ -92,7 +92,7 @@ export class CwNativeTokenAdapter } } -export type CW20Metadata = ERC20Metadata; +export type CW20Metadata = TokenMetadata; type CW20Response = TokenInfoResponse | BalanceResponse; // Interacts with CW20/721 contracts diff --git a/typescript/sdk/src/token/adapters/CosmosTokenAdapter.ts b/typescript/sdk/src/token/adapters/CosmosTokenAdapter.ts index de0bfd2ee..ea85d05aa 100644 --- a/typescript/sdk/src/token/adapters/CosmosTokenAdapter.ts +++ b/typescript/sdk/src/token/adapters/CosmosTokenAdapter.ts @@ -6,7 +6,7 @@ import { Address, Domain, assert } from '@hyperlane-xyz/utils'; import { BaseCosmosAdapter } from '../../app/MultiProtocolApp.js'; import { MultiProtocolProvider } from '../../providers/MultiProtocolProvider.js'; import { ChainName } from '../../types.js'; -import { MinimalTokenMetadata } from '../config.js'; +import { TokenMetadata } from '../types.js'; import { CwHypCollateralAdapter } from './CosmWasmTokenAdapter.js'; import { @@ -43,7 +43,7 @@ export class CosmNativeTokenAdapter return BigInt(coin.amount); } - getMetadata(): Promise { + getMetadata(): Promise { throw new Error('Metadata not available to native tokens'); } diff --git a/typescript/sdk/src/token/adapters/EvmTokenAdapter.ts b/typescript/sdk/src/token/adapters/EvmTokenAdapter.ts index febbd57a9..3fc25ade3 100644 --- a/typescript/sdk/src/token/adapters/EvmTokenAdapter.ts +++ b/typescript/sdk/src/token/adapters/EvmTokenAdapter.ts @@ -21,7 +21,7 @@ import { import { BaseEvmAdapter } from '../../app/MultiProtocolApp.js'; import { MultiProtocolProvider } from '../../providers/MultiProtocolProvider.js'; import { ChainName } from '../../types.js'; -import { MinimalTokenMetadata } from '../config.js'; +import { TokenMetadata } from '../types.js'; import { IHypTokenAdapter, @@ -45,7 +45,7 @@ export class EvmNativeTokenAdapter return BigInt(balance.toString()); } - async getMetadata(): Promise { + async getMetadata(): Promise { // TODO get metadata from chainMetadata config throw new Error('Metadata not available to native tokens'); } @@ -98,13 +98,14 @@ export class EvmTokenAdapter return BigInt(balance.toString()); } - override async getMetadata(isNft?: boolean): Promise { - const [decimals, symbol, name] = await Promise.all([ + override async getMetadata(isNft?: boolean): Promise { + const [decimals, symbol, name, totalSupply] = await Promise.all([ isNft ? 0 : this.contract.decimals(), this.contract.symbol(), this.contract.name(), + this.contract.totalSupply(), ]); - return { decimals, symbol, name }; + return { decimals, symbol, name, totalSupply: totalSupply.toString() }; } override async isApproveRequired( @@ -247,7 +248,7 @@ export class EvmHypCollateralAdapter }); } - override getMetadata(isNft?: boolean): Promise { + override getMetadata(isNft?: boolean): Promise { return this.getWrappedTokenAdapter().then((t) => t.getMetadata(isNft)); } diff --git a/typescript/sdk/src/token/adapters/ITokenAdapter.ts b/typescript/sdk/src/token/adapters/ITokenAdapter.ts index f5dfd906a..67bd1a0f4 100644 --- a/typescript/sdk/src/token/adapters/ITokenAdapter.ts +++ b/typescript/sdk/src/token/adapters/ITokenAdapter.ts @@ -1,6 +1,6 @@ import { Address, Domain, Numberish } from '@hyperlane-xyz/utils'; -import { MinimalTokenMetadata } from '../config.js'; +import { TokenMetadata } from '../types.js'; export interface TransferParams { weiAmountOrId: Numberish; @@ -23,7 +23,7 @@ export interface InterchainGasQuote { export interface ITokenAdapter { getBalance(address: Address): Promise; - getMetadata(isNft?: boolean): Promise; + getMetadata(isNft?: boolean): Promise; isApproveRequired( owner: Address, spender: Address, diff --git a/typescript/sdk/src/token/adapters/SealevelTokenAdapter.ts b/typescript/sdk/src/token/adapters/SealevelTokenAdapter.ts index a924bc92c..5cae1811f 100644 --- a/typescript/sdk/src/token/adapters/SealevelTokenAdapter.ts +++ b/typescript/sdk/src/token/adapters/SealevelTokenAdapter.ts @@ -33,7 +33,7 @@ import { SealevelAccountDataWrapper, SealevelInstructionWrapper, } from '../../utils/sealevelSerialization.js'; -import { MinimalTokenMetadata } from '../config.js'; +import { TokenMetadata } from '../types.js'; import { IHypTokenAdapter, @@ -61,7 +61,7 @@ export class SealevelNativeTokenAdapter return BigInt(balance.toString()); } - async getMetadata(): Promise { + async getMetadata(): Promise { throw new Error('Metadata not available to native tokens'); } @@ -115,9 +115,9 @@ export class SealevelTokenAdapter return BigInt(response.value.amount); } - async getMetadata(_isNft?: boolean): Promise { + async getMetadata(_isNft?: boolean): Promise { // TODO solana support - return { decimals: 9, symbol: 'SPL', name: 'SPL Token' }; + return { decimals: 9, symbol: 'SPL', name: 'SPL Token', totalSupply: '' }; } async isApproveRequired(): Promise { @@ -212,11 +212,12 @@ export abstract class SealevelHypTokenAdapter return this.cachedTokenAccountData; } - override async getMetadata(): Promise { + override async getMetadata(): Promise { const tokenData = await this.getTokenAccountData(); // TODO full token metadata support return { decimals: tokenData.decimals, + totalSupply: '0', symbol: 'HYP', name: 'Unknown Hyp Token', }; @@ -506,7 +507,7 @@ export class SealevelHypNativeAdapter extends SealevelHypTokenAdapter { return this.wrappedNative.getBalance(owner); } - override async getMetadata(): Promise { + override async getMetadata(): Promise { return this.wrappedNative.getMetadata(); } diff --git a/typescript/sdk/src/token/app.ts b/typescript/sdk/src/token/app.ts index 9ea8141fa..0eeb41dbc 100644 --- a/typescript/sdk/src/token/app.ts +++ b/typescript/sdk/src/token/app.ts @@ -10,11 +10,7 @@ import { import { MultiProvider } from '../providers/MultiProvider.js'; import { GasRouterApp } from '../router/RouterApps.js'; -import { - HypERC20Factories, - hypERC20Tokenfactories, - hypERC20factories, -} from './contracts.js'; +import { HypERC20Factories, hypERC20factories } from './contracts.js'; export class HypERC20App extends GasRouterApp { constructor( @@ -25,7 +21,7 @@ export class HypERC20App extends GasRouterApp { } router(contracts: HyperlaneContracts): TokenRouter { - for (const key of objKeys(hypERC20Tokenfactories)) { + for (const key of objKeys(hypERC20factories)) { if (contracts[key]) { return contracts[key] as unknown as TokenRouter; } diff --git a/typescript/sdk/src/token/checker.ts b/typescript/sdk/src/token/checker.ts index 9b0e366f8..48b38841f 100644 --- a/typescript/sdk/src/token/checker.ts +++ b/typescript/sdk/src/token/checker.ts @@ -8,20 +8,19 @@ import { HyperlaneRouterChecker } from '../router/HyperlaneRouterChecker.js'; import { ChainName } from '../types.js'; import { HypERC20App } from './app.js'; +import { HypERC20Factories } from './contracts.js'; import { - ERC20RouterConfig, - HypERC20Config, - TokenMetadata, + TokenRouterConfig, isCollateralConfig, isNativeConfig, isSyntheticConfig, -} from './config.js'; -import { HypERC20Factories } from './contracts.js'; +} from './schemas.js'; +import { TokenMetadata } from './types.js'; export class HypERC20Checker extends HyperlaneRouterChecker< HypERC20Factories, HypERC20App, - ERC20RouterConfig + TokenRouterConfig > { async checkChain(chain: ChainName): Promise { await super.checkChain(chain); @@ -31,10 +30,10 @@ export class HypERC20Checker extends HyperlaneRouterChecker< async checkToken(chain: ChainName): Promise { const checkERC20 = async ( token: ERC20, - config: HypERC20Config, + config: TokenRouterConfig, ): Promise => { const checks: { - method: keyof TokenMetadata | 'decimals'; + method: keyof ERC20 & keyof TokenMetadata; violationType: string; }[] = [ { method: 'symbol', violationType: 'TokenSymbolMismatch' }, diff --git a/typescript/sdk/src/token/config.ts b/typescript/sdk/src/token/config.ts index e8d57a4a7..a68b2a5a9 100644 --- a/typescript/sdk/src/token/config.ts +++ b/typescript/sdk/src/token/config.ts @@ -1,17 +1,11 @@ -import { ethers } from 'ethers'; -import z from 'zod'; - -import { GasRouterConfig } from '../router/types.js'; - -import { SyntheticConfigSchema } from './schemas.js'; - export enum TokenType { synthetic = 'synthetic', fastSynthetic = 'fastSynthetic', syntheticUri = 'syntheticUri', collateral = 'collateral', collateralVault = 'collateralVault', - collateralXERC20 = 'collateralXERC20', + XERC20 = 'xERC20', + XERC20Lockbox = 'xERC20Lockbox', collateralFiat = 'collateralFiat', fastCollateral = 'fastCollateral', collateralUri = 'collateralUri', @@ -19,86 +13,14 @@ export enum TokenType { nativeScaled = 'nativeScaled', } -export type TokenMetadata = { - name: string; - symbol: string; - totalSupply: ethers.BigNumberish; -}; - -export type TokenDecimals = { - decimals: number; - scale?: number; +export const gasOverhead = (tokenType: TokenType) => { + switch (tokenType) { + case TokenType.fastSynthetic: + case TokenType.synthetic: + return 64_000; + case TokenType.native: + return 44_000; + default: + return 68_000; + } }; - -export type ERC20Metadata = TokenMetadata & TokenDecimals; -export type MinimalTokenMetadata = Omit; - -export const isTokenMetadata = (metadata: any): metadata is TokenMetadata => - metadata.name && metadata.symbol && metadata.totalSupply !== undefined; // totalSupply can be 0 - -export const isErc20Metadata = (metadata: any): metadata is ERC20Metadata => - metadata.decimals && isTokenMetadata(metadata); - -export type SyntheticConfig = z.infer; -export type CollateralConfig = { - type: - | TokenType.collateral - | TokenType.collateralXERC20 - | TokenType.collateralFiat - | TokenType.collateralUri - | TokenType.fastCollateral - | TokenType.fastSynthetic - | TokenType.collateralVault; - token: string; -} & Partial; -export type NativeConfig = { - type: TokenType.native; -} & Partial; - -export type TokenConfig = SyntheticConfig | CollateralConfig | NativeConfig; - -export const isCollateralConfig = ( - config: TokenConfig, -): config is CollateralConfig => - config.type === TokenType.collateral || - config.type === TokenType.collateralXERC20 || - config.type === TokenType.collateralFiat || - config.type === TokenType.collateralUri || - config.type === TokenType.fastCollateral || - config.type == TokenType.collateralVault; - -export const isCollateralVaultConfig = ( - config: TokenConfig, -): config is CollateralConfig => config.type === TokenType.collateralVault; - -export const isSyntheticConfig = ( - config: TokenConfig, -): config is SyntheticConfig => - config.type === TokenType.synthetic || - config.type === TokenType.syntheticUri || - config.type === TokenType.fastSynthetic; - -export const isNativeConfig = (config: TokenConfig): config is NativeConfig => - config.type === TokenType.native; - -export const isUriConfig = (config: TokenConfig): boolean => - config.type === TokenType.syntheticUri || - config.type === TokenType.collateralUri; - -export const isFastConfig = (config: TokenConfig): boolean => - config.type === TokenType.fastSynthetic || - config.type === TokenType.fastCollateral; - -export type HypERC20Config = GasRouterConfig & SyntheticConfig & ERC20Metadata; -export type HypERC20CollateralConfig = GasRouterConfig & - CollateralConfig & - Partial; -export type HypNativeConfig = GasRouterConfig & NativeConfig; -export type ERC20RouterConfig = - | HypERC20Config - | HypERC20CollateralConfig - | HypNativeConfig; - -export type HypERC721Config = GasRouterConfig & SyntheticConfig; -export type HypERC721CollateralConfig = GasRouterConfig & CollateralConfig; -export type ERC721RouterConfig = HypERC721Config | HypERC721CollateralConfig; diff --git a/typescript/sdk/src/token/contracts.ts b/typescript/sdk/src/token/contracts.ts index 50ef36dbb..d9982522c 100644 --- a/typescript/sdk/src/token/contracts.ts +++ b/typescript/sdk/src/token/contracts.ts @@ -8,14 +8,13 @@ import { HypERC721URICollateral__factory, HypERC721URIStorage__factory, HypERC721__factory, - HypFiatTokenCollateral__factory, + HypFiatToken__factory, HypNativeScaled__factory, HypNative__factory, - HypXERC20Collateral__factory, + HypXERC20Lockbox__factory, + HypXERC20__factory, } from '@hyperlane-xyz/core'; -import { proxiedFactories } from '../router/types.js'; - import { TokenType } from './config.js'; export const hypERC20contracts = { @@ -23,30 +22,27 @@ export const hypERC20contracts = { [TokenType.fastSynthetic]: 'FastHypERC20', [TokenType.synthetic]: 'HypERC20', [TokenType.collateral]: 'HypERC20Collateral', - [TokenType.collateralFiat]: 'HypFiatTokenCollateral', - [TokenType.collateralXERC20]: 'HypXERC20Collateral', + [TokenType.collateralFiat]: 'HypFiatToken', + [TokenType.XERC20]: 'HypXERC20', + [TokenType.XERC20Lockbox]: 'HypXERC20Lockbox', [TokenType.collateralVault]: 'HypERC20CollateralVaultDeposit', [TokenType.native]: 'HypNative', [TokenType.nativeScaled]: 'HypNativeScaled', }; export type HypERC20contracts = typeof hypERC20contracts; -export const hypERC20Tokenfactories = { +export const hypERC20factories = { [TokenType.fastCollateral]: new FastHypERC20Collateral__factory(), [TokenType.fastSynthetic]: new FastHypERC20__factory(), [TokenType.synthetic]: new HypERC20__factory(), [TokenType.collateral]: new HypERC20Collateral__factory(), [TokenType.collateralVault]: new HypERC20CollateralVaultDeposit__factory(), - [TokenType.collateralFiat]: new HypFiatTokenCollateral__factory(), - [TokenType.collateralXERC20]: new HypXERC20Collateral__factory(), + [TokenType.collateralFiat]: new HypFiatToken__factory(), + [TokenType.XERC20]: new HypXERC20__factory(), + [TokenType.XERC20Lockbox]: new HypXERC20Lockbox__factory(), [TokenType.native]: new HypNative__factory(), [TokenType.nativeScaled]: new HypNativeScaled__factory(), }; - -export const hypERC20factories = { - ...hypERC20Tokenfactories, - ...proxiedFactories, -}; export type HypERC20Factories = typeof hypERC20factories; export const hypERC721contracts = { @@ -63,7 +59,6 @@ export const hypERC721factories = { [TokenType.collateral]: new HypERC721Collateral__factory(), [TokenType.syntheticUri]: new HypERC721URIStorage__factory(), [TokenType.synthetic]: new HypERC721__factory(), - ...proxiedFactories, }; export type HypERC721Factories = typeof hypERC721factories; diff --git a/typescript/sdk/src/token/deploy.hardhat-test.ts b/typescript/sdk/src/token/deploy.hardhat-test.ts index 25ed9e50b..49323191d 100644 --- a/typescript/sdk/src/token/deploy.hardhat-test.ts +++ b/typescript/sdk/src/token/deploy.hardhat-test.ts @@ -2,38 +2,31 @@ import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers.js'; import { expect } from 'chai'; import hre from 'hardhat'; -import { - ERC20Test, - ERC20Test__factory, - Mailbox__factory, -} from '@hyperlane-xyz/core'; -import { RouterConfig, TestChainName } from '@hyperlane-xyz/sdk'; -import { objMap } from '@hyperlane-xyz/utils'; +import { ERC20Test__factory } from '@hyperlane-xyz/core'; +import { Address, objMap } from '@hyperlane-xyz/utils'; +import { TestChainName } from '../consts/testChains.js'; import { TestCoreApp } from '../core/TestCoreApp.js'; import { TestCoreDeployer } from '../core/TestCoreDeployer.js'; import { HyperlaneProxyFactoryDeployer } from '../deploy/HyperlaneProxyFactoryDeployer.js'; import { HyperlaneIsmFactory } from '../ism/HyperlaneIsmFactory.js'; import { MultiProvider } from '../providers/MultiProvider.js'; -import { ChainMap } from '../types.js'; -import { EvmERC20WarpRouteReader } from './EvmERC20WarpRouteReader.js'; -import { - HypERC20CollateralConfig, - HypERC20Config, - TokenConfig, - TokenType, -} from './config.js'; +import { EvmWarpRouteReader } from './EvmERC20WarpRouteReader.js'; +import { TokenType } from './config.js'; import { HypERC20Deployer } from './deploy.js'; +import { TokenRouterConfig } from './schemas.js'; import { WarpRouteDeployConfig } from './types.js'; +const chain = TestChainName.test1; + describe('TokenDeployer', async () => { let signer: SignerWithAddress; let deployer: HypERC20Deployer; let multiProvider: MultiProvider; let coreApp: TestCoreApp; - let routerConfigMap: ChainMap; let config: WarpRouteDeployConfig; + let token: Address; before(async () => { [signer] = await hre.ethers.getSigners(); @@ -44,19 +37,27 @@ describe('TokenDeployer', async () => { ); const ismFactory = new HyperlaneIsmFactory(factories, multiProvider); coreApp = await new TestCoreDeployer(multiProvider, ismFactory).deployApp(); - routerConfigMap = coreApp.getRouterConfig(signer.address); + const routerConfigMap = coreApp.getRouterConfig(signer.address); config = objMap( routerConfigMap, - (chain, c): HypERC20Config => ({ + (chain, c): TokenRouterConfig => ({ type: TokenType.synthetic, name: chain, symbol: `u${chain}`, decimals: 18, - totalSupply: 100_000, - gas: 65_000, + totalSupply: '100000', ...c, }), ); + + const { name, decimals, symbol, totalSupply } = config[chain]; + const contract = await new ERC20Test__factory(signer).deploy( + name!, + symbol!, + totalSupply!, + decimals!, + ); + token = contract.address; }); beforeEach(async () => { @@ -64,66 +65,36 @@ describe('TokenDeployer', async () => { }); it('deploys', async () => { - await deployer.deploy(config as ChainMap); + await deployer.deploy(config); }); - describe('ERC20WarpRouterReader', async () => { - const TOKEN_NAME = 'fake'; - const TOKEN_SUPPLY = '100000000000000000000'; - const TOKEN_DECIMALS = 18; - let erc20Factory: ERC20Test__factory; - let token: ERC20Test; + for (const type of [TokenType.collateral, TokenType.synthetic]) { + describe('ERC20WarpRouterReader', async () => { + let reader: EvmWarpRouteReader; + let routerAddress: Address; - before(async () => { - erc20Factory = new ERC20Test__factory(signer); - token = await erc20Factory.deploy( - TOKEN_NAME, - TOKEN_NAME, - TOKEN_SUPPLY, - TOKEN_DECIMALS, - ); - }); - async function deriveWarpConfig(chainName: string, address: string) { - return new EvmERC20WarpRouteReader( - multiProvider, - chainName, - ).deriveWarpRouteConfig(address); - } - it('should derive ERC20RouterConfig from collateral correctly', async () => { - const baseConfig = routerConfigMap[TestChainName.test1]; - const mailbox = Mailbox__factory.connect(baseConfig.mailbox, signer); + before(() => { + reader = new EvmWarpRouteReader(multiProvider, TestChainName.test1); + }); - // Create config - const config: { [key: string]: any } = { - [TestChainName.test1]: { - type: TokenType.collateral, - token: token.address, - hook: await mailbox.defaultHook(), - gas: 65_000, - ...baseConfig, - }, - }; - // Deploy with config - const warpRoute = await deployer.deploy( - config as ChainMap, - ); + beforeEach(async () => { + config[chain] = { + ...config[chain], + type, + // @ts-ignore + token: type === TokenType.collateral ? token : undefined, + }; + const warpRoute = await deployer.deploy(config); + routerAddress = warpRoute[chain][type].address; + }); - // Derive config and check if each value matches - const derivedConfig: Partial = - await deriveWarpConfig( - TestChainName.test1, - warpRoute[TestChainName.test1].collateral.address, + it(`should derive TokenRouterConfig from ${type} correctly`, async () => { + const derivedConfig = await reader.deriveWarpRouteConfig( + routerAddress, + type, ); - - for (const [key, value] of Object.entries(derivedConfig)) { - const deployedValue = config[TestChainName.test1][key]; - if (deployedValue) expect(deployedValue).to.equal(value); - } - - // Check if token values matches - expect(derivedConfig.name).to.equal(TOKEN_NAME); - expect(derivedConfig.symbol).to.equal(TOKEN_NAME); - expect(derivedConfig.decimals).to.equal(TOKEN_DECIMALS); + expect(derivedConfig).to.include(config[chain]); + }); }); - }); + } }); diff --git a/typescript/sdk/src/token/deploy.ts b/typescript/sdk/src/token/deploy.ts index f4f4e93b9..ad865c4f0 100644 --- a/typescript/sdk/src/token/deploy.ts +++ b/typescript/sdk/src/token/deploy.ts @@ -1,92 +1,70 @@ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ -import { constants, providers } from 'ethers'; +import { constants } from 'ethers'; import { ERC20__factory, - ERC721EnumerableUpgradeable__factory, + ERC721Enumerable__factory, GasRouter, - MailboxClient, } from '@hyperlane-xyz/core'; -import { objKeys, objMap, rootLogger } from '@hyperlane-xyz/utils'; +import { assert, objKeys, objMap, rootLogger } from '@hyperlane-xyz/utils'; import { HyperlaneContracts } from '../contracts/types.js'; import { ContractVerifier } from '../deploy/verify/ContractVerifier.js'; import { HyperlaneIsmFactory } from '../ism/HyperlaneIsmFactory.js'; import { MultiProvider } from '../providers/MultiProvider.js'; import { GasRouterDeployer } from '../router/GasRouterDeployer.js'; -import { GasConfig, RouterConfig } from '../router/types.js'; -import { ChainMap, ChainName } from '../types.js'; +import { ChainName } from '../types.js'; -import { - CollateralConfig, - ERC20Metadata, - ERC20RouterConfig, - ERC721RouterConfig, - HypERC20Config, - HypERC721Config, - TokenConfig, - TokenMetadata, - TokenType, - isCollateralConfig, - isErc20Metadata, - isNativeConfig, - isSyntheticConfig, - isTokenMetadata, - isUriConfig, -} from './config.js'; +import { gasOverhead } from './config.js'; import { HypERC20Factories, - HypERC20contracts, HypERC721Factories, - HypERC721contracts, + TokenFactories, hypERC20contracts, hypERC20factories, hypERC721contracts, hypERC721factories, } from './contracts.js'; +import { + TokenRouterConfig, + isCollateralConfig, + isNativeConfig, + isSyntheticConfig, + isTokenMetadata, +} from './schemas.js'; +import { TokenMetadata, WarpRouteDeployConfig } from './types.js'; -export class HypERC20Deployer extends GasRouterDeployer< - ERC20RouterConfig, - HypERC20Factories -> { +abstract class TokenDeployer< + Factories extends TokenFactories, +> extends GasRouterDeployer { constructor( multiProvider: MultiProvider, + factories: Factories, + loggerName: string, ismFactory?: HyperlaneIsmFactory, contractVerifier?: ContractVerifier, ) { - super(multiProvider, hypERC20factories, { - logger: rootLogger.child({ module: 'HypERC20Deployer' }), + super(multiProvider, factories, { + logger: rootLogger.child({ module: loggerName }), ismFactory, contractVerifier, }); // factories not used in deploy } - routerContractName(config: ERC20RouterConfig): string { - return hypERC20contracts[this.routerContractKey(config)]; - } - - routerContractKey(config: ERC20RouterConfig) { - return config.type as keyof HypERC20contracts; - } - - async constructorArgs( - _: ChainName, - config: ERC20RouterConfig, - ): Promise> { + async constructorArgs(_: ChainName, config: TokenRouterConfig): Promise { if (isCollateralConfig(config)) { - return [config.token, config.mailbox] as any; + return [config.token, config.mailbox]; } else if (isNativeConfig(config)) { - return config.scale - ? [config.scale, config.mailbox] - : ([config.mailbox] as any); + return config.scale ? [config.scale, config.mailbox] : [config.mailbox]; } else if (isSyntheticConfig(config)) { - return [config.decimals, config.mailbox] as any; + assert(config.decimals); // decimals must be defined by this point + return [config.decimals, config.mailbox]; } else { - throw new Error('Unknown collateral type when constructing arguments'); + throw new Error('Unknown token type when constructing arguments'); } } - async initializeArgs(_: ChainName, config: HypERC20Config): Promise { + async initializeArgs(_: ChainName, config: TokenRouterConfig): Promise { // ISM config can be an object, but is not supported right now. if (typeof config.interchainSecurityModule === 'object') { throw new Error('Token deployer does not support ISM objects currently'); @@ -96,318 +74,144 @@ export class HypERC20Deployer extends GasRouterDeployer< config.interchainSecurityModule ?? constants.AddressZero, config.owner, ]; - if (isCollateralConfig(config)) { - return defaultArgs as any; - } else if (isNativeConfig(config)) { - return defaultArgs as any; + if (isCollateralConfig(config) || isNativeConfig(config)) { + return defaultArgs; } else if (isSyntheticConfig(config)) { - return [ - config.totalSupply, - config.name, - config.symbol, - ...defaultArgs, - ] as any; + return [config.totalSupply, config.name, config.symbol, ...defaultArgs]; } else { throw new Error('Unknown collateral type when initializing arguments'); } } - static async fetchMetadata( - provider: providers.Provider, - config: CollateralConfig, - ): Promise { - const erc20 = ERC20__factory.connect(config.token, provider); - - const [name, symbol, totalSupply, decimals] = await Promise.all([ - erc20.name(), - erc20.symbol(), - erc20.totalSupply(), - erc20.decimals(), - ]); - - return { name, symbol, totalSupply, decimals }; - } - - static gasOverheadDefault(config: TokenConfig): number { - switch (config.type) { - case 'fastSynthetic': - return 64_000; - case 'synthetic': - return 64_000; - case 'native': - return 44_000; - case 'collateral': - case 'fastCollateral': - default: - return 68_000; - } - } - - // Gets the metadata for a collateral token, favoring the config - // and getting any on-chain metadata that is missing. - async getCollateralMetadata( - chain: ChainName, - config: CollateralConfig, - ): Promise { - const metadata = { - name: config.name, - symbol: config.symbol, - decimals: config.decimals, - totalSupply: 0, - }; - - if ( - metadata.name && - metadata.symbol && - metadata.decimals !== undefined && - metadata.decimals !== null - ) { - return metadata as ERC20Metadata; - } - const fetchedMetadata = await HypERC20Deployer.fetchMetadata( - this.multiProvider.getProvider(chain), - config, - ); - // Filter out undefined values - const definedConfigMetadata = Object.fromEntries( - Object.entries(metadata).filter(([k, v]) => !!k && !!v), - ); - return { - ...fetchedMetadata, - ...definedConfigMetadata, - } as ERC20Metadata; - } - - router(contracts: HyperlaneContracts) { - for (const key of objKeys(hypERC20factories)) { - if (contracts[key]) { - return contracts[key] as GasRouter; + static async deriveTokenMetadata( + multiProvider: MultiProvider, + configMap: WarpRouteDeployConfig, + ): Promise { + for (const [chain, config] of Object.entries(configMap)) { + if (isTokenMetadata(config)) { + return config; } - } - throw new Error('No matching contract found'); - } - async deployContracts(chain: ChainName, config: HypERC20Config) { - const deployedContracts = await super.deployContracts(chain, config); - const router = deployedContracts[this.routerContractKey(config)]; - await this.configureClient(chain, router, config); - return { - [config.type]: router, - ...deployedContracts, - } as any; - } - - async buildTokenMetadata( - configMap: ChainMap, - ): Promise> { - let tokenMetadata: ERC20Metadata | undefined; + if (isNativeConfig(config)) { + const nativeToken = multiProvider.getChainMetadata(chain).nativeToken; + if (nativeToken) { + return { totalSupply: 0, ...nativeToken }; + } + } - for (const [chain, config] of Object.entries(configMap)) { if (isCollateralConfig(config)) { - const collateralMetadata = await this.getCollateralMetadata( - chain, - config, - ); - tokenMetadata = { - ...collateralMetadata, - totalSupply: 0, - }; - } else if (isNativeConfig(config)) { - const chainMetadata = this.multiProvider.getChainMetadata(chain); - if (chainMetadata.nativeToken) { - tokenMetadata = { - ...chainMetadata.nativeToken, - totalSupply: 0, - }; - } else { - throw new Error( - `Warp route config specifies native token but chain metadata for ${chain} does not provide native token details`, + const provider = multiProvider.getProvider(chain); + + if (config.isNft) { + const erc721 = ERC721Enumerable__factory.connect( + config.token, + provider, ); + const [name, symbol, totalSupply] = await Promise.all([ + erc721.name(), + erc721.symbol(), + erc721.totalSupply(), + ]); + return { + name, + symbol, + totalSupply: totalSupply.toString(), + }; } - } else if (isErc20Metadata(config)) { - tokenMetadata = config; - } - } - if (!isErc20Metadata(tokenMetadata)) { - throw new Error('Invalid ERC20 token metadata'); + const erc20 = ERC20__factory.connect(config.token, provider); + const [name, symbol, totalSupply, decimals] = await Promise.all([ + erc20.name(), + erc20.symbol(), + erc20.totalSupply(), + erc20.decimals(), + ]); + + return { name, symbol, totalSupply: totalSupply.toString(), decimals }; + } } - return objMap(configMap, () => tokenMetadata!); + return undefined; } - buildGasOverhead(configMap: ChainMap): ChainMap { - return objMap(configMap, (_, config) => ({ - gas: HypERC20Deployer.gasOverheadDefault(config), + async deploy(configMap: WarpRouteDeployConfig) { + const tokenMetadata = await TokenDeployer.deriveTokenMetadata( + this.multiProvider, + configMap, + ); + const resolvedConfigMap = objMap(configMap, (_, config) => ({ + ...tokenMetadata, + gas: gasOverhead(config.type), + ...config, })); - } - - async deploy(configMap: ChainMap) { - const tokenMetadata = await this.buildTokenMetadata(configMap); - const gasOverhead = this.buildGasOverhead(configMap); - const mergedConfig = objMap(configMap, (chain, config) => { - return { - ...tokenMetadata[chain], - ...gasOverhead[chain], - ...config, - }; - }) as ChainMap; - - return super.deploy(mergedConfig); + return super.deploy(resolvedConfigMap); } } -export class HypERC721Deployer extends GasRouterDeployer< - ERC721RouterConfig, - HypERC721Factories -> { +export class HypERC20Deployer extends TokenDeployer { constructor( multiProvider: MultiProvider, + ismFactory?: HyperlaneIsmFactory, contractVerifier?: ContractVerifier, ) { - super(multiProvider, hypERC721factories, { - logger: rootLogger.child({ module: 'HypERC721Deployer' }), + super( + multiProvider, + hypERC20factories, + 'HypERC20Deployer', + ismFactory, contractVerifier, - }); - } - routerContractName(config: ERC721RouterConfig): string { - return hypERC721contracts[this.routerContractKey(config)]; + ); } - routerContractKey( - config: ERC721RouterConfig, - ): K { - if (isCollateralConfig(config)) { - return ( - isUriConfig(config) ? TokenType.collateralUri : TokenType.collateral - ) as K; - } else { - // if isSyntheticConfig - return ( - isUriConfig(config) ? TokenType.syntheticUri : TokenType.synthetic - ) as K; + router(contracts: HyperlaneContracts): GasRouter { + for (const key of objKeys(hypERC20factories)) { + if (contracts[key]) { + return contracts[key]; + } } + throw new Error('No matching contract found'); } - async constructorArgs( - _: ChainName, - config: ERC721RouterConfig, - ): Promise { - if (isCollateralConfig(config)) { - return [config.token, config.mailbox]; - } else if (isSyntheticConfig(config)) { - return [config.mailbox]; - } else { - throw new Error('Unknown collateral type when constructing arguments'); - } + routerContractKey(config: TokenRouterConfig): keyof HypERC20Factories { + assert(config.type in hypERC20factories, 'Invalid ERC20 token type'); + return config.type as keyof HypERC20Factories; } - async initializeArgs(_: ChainName, config: ERC721RouterConfig): Promise { - const defaultArgs = [ - config.hook ?? constants.AddressZero, - config.interchainSecurityModule ?? constants.AddressZero, - config.owner, - ]; - if (isCollateralConfig(config)) { - return defaultArgs; - } else if (isSyntheticConfig(config)) { - return [config.totalSupply, config.name, config.symbol, ...defaultArgs]; - } else { - throw new Error('Unknown collateral type when initializing arguments'); - } + routerContractName(config: TokenRouterConfig): string { + return hypERC20contracts[this.routerContractKey(config)]; } +} - static async fetchMetadata( - provider: providers.Provider, - config: CollateralConfig, - ): Promise { - const erc721 = ERC721EnumerableUpgradeable__factory.connect( - config.token, - provider, +export class HypERC721Deployer extends TokenDeployer { + constructor( + multiProvider: MultiProvider, + ismFactory?: HyperlaneIsmFactory, + contractVerifier?: ContractVerifier, + ) { + super( + multiProvider, + hypERC721factories, + 'HypERC721Deployer', + ismFactory, + contractVerifier, ); - const [name, symbol, totalSupply] = await Promise.all([ - erc721.name(), - erc721.symbol(), - erc721.totalSupply(), - ]); - - return { name, symbol, totalSupply }; } - static gasOverheadDefault(config: TokenConfig): number { - switch (config.type) { - case 'synthetic': - return 160_000; - case 'syntheticUri': - return 163_000; - case 'collateral': - case 'collateralUri': - default: - return 80_000; - } - } - - router(contracts: HyperlaneContracts) { + router(contracts: HyperlaneContracts): GasRouter { for (const key of objKeys(hypERC721factories)) { if (contracts[key]) { - return contracts[key] as GasRouter; + return contracts[key]; } } throw new Error('No matching contract found'); } - async deployContracts(chain: ChainName, config: HypERC721Config) { - const { [this.routerContractKey(config)]: router } = - await super.deployContracts(chain, config); - - await this.configureClient(chain, router as MailboxClient, config); - return { [config.type]: router } as any; - } - - async buildTokenMetadata( - configMap: ChainMap, - ): Promise> { - let tokenMetadata: TokenMetadata | undefined; - - for (const [chain, config] of Object.entries(configMap)) { - if (isCollateralConfig(config)) { - const collateralMetadata = await HypERC721Deployer.fetchMetadata( - this.multiProvider.getProvider(chain), - config, - ); - tokenMetadata = { - ...collateralMetadata, - totalSupply: 0, - }; - } else if (isTokenMetadata(config)) { - tokenMetadata = config; - } - } - - if (!isTokenMetadata(tokenMetadata)) { - throw new Error('Invalid ERC721 token metadata'); - } - - return objMap(configMap, () => tokenMetadata!); - } - - buildGasOverhead(configMap: ChainMap): ChainMap { - return objMap(configMap, (_, config) => ({ - gas: HypERC721Deployer.gasOverheadDefault(config), - })); + routerContractKey(config: TokenRouterConfig): keyof HypERC721Factories { + assert(config.type in hypERC721factories, 'Invalid ERC721 token type'); + return config.type as keyof HypERC721Factories; } - async deploy(configMap: ChainMap) { - const tokenMetadata = await this.buildTokenMetadata(configMap); - const gasOverhead = this.buildGasOverhead(configMap); - const mergedConfig = objMap(configMap, (chain, config) => { - return { - ...tokenMetadata[chain], - ...gasOverhead[chain], - ...config, - }; - }) as ChainMap; - - return super.deploy(mergedConfig); + routerContractName(config: TokenRouterConfig): string { + return hypERC721contracts[this.routerContractKey(config)]; } } diff --git a/typescript/sdk/src/token/schemas.test.ts b/typescript/sdk/src/token/schemas.test.ts index 394b4c9fb..65f780cd0 100644 --- a/typescript/sdk/src/token/schemas.test.ts +++ b/typescript/sdk/src/token/schemas.test.ts @@ -1,8 +1,8 @@ import { expect } from 'chai'; import { ethers } from 'ethers'; -import { constants } from 'ethers'; -import { TokenType, WarpRouteDeployConfigSchema } from '@hyperlane-xyz/sdk'; +import { TokenType } from './config.js'; +import { WarpRouteDeployConfigSchema } from './schemas.js'; const SOME_ADDRESS = ethers.Wallet.createRandom().address; const COLLATERAL_TYPES = [ @@ -16,58 +16,43 @@ const NON_COLLATERAL_TYPES = [ TokenType.synthetic, TokenType.syntheticUri, TokenType.fastSynthetic, - TokenType.native, ]; describe('WarpRouteDeployConfigSchema refine', () => { - it('should require type address', () => { - const config: any = { + let config: any; + beforeEach(() => { + config = { arbitrum: { type: TokenType.collateral, token: SOME_ADDRESS, + owner: SOME_ADDRESS, + mailbox: SOME_ADDRESS, }, }; + }); + + it('should require token type', () => { expect(WarpRouteDeployConfigSchema.safeParse(config).success).to.be.true; delete config.arbitrum.type; expect(WarpRouteDeployConfigSchema.safeParse(config).success).to.be.false; }); it('should require token address', () => { - const config: any = { - arbitrum: { - type: TokenType.collateral, - token: SOME_ADDRESS, - }, - }; expect(WarpRouteDeployConfigSchema.safeParse(config).success).to.be.true; delete config.arbitrum.token; expect(WarpRouteDeployConfigSchema.safeParse(config).success).to.be.false; }); - it('should allow mailbox to be optional', () => { - const config: any = { - arbitrum: { - type: TokenType.collateral, - token: constants.AddressZero, - mailbox: SOME_ADDRESS, - }, - }; + it('should require mailbox address', () => { expect(WarpRouteDeployConfigSchema.safeParse(config).success).to.be.true; delete config.arbitrum.mailbox; - expect(WarpRouteDeployConfigSchema.safeParse(config).success).to.be.true; + expect(WarpRouteDeployConfigSchema.safeParse(config).success).to.be.false; }); it('should throw if collateral type and token is empty', async () => { for (const type of COLLATERAL_TYPES) { - const config: any = { - arbitrum: { - type, - mailbox: SOME_ADDRESS, - name: 'Arby Coin', - symbol: 'ARBY', - totalSupply: '10000', - }, - }; + config.arbitrum.type = type; + config.arbitrum.token = undefined; expect(WarpRouteDeployConfigSchema.safeParse(config).success).to.be.false; // Set to some address @@ -76,17 +61,23 @@ describe('WarpRouteDeployConfigSchema refine', () => { } }); - it('should succeed if non-collateral type and token is empty', async () => { + it('should accept native type if token is empty', async () => { + config.arbitrum.type = TokenType.native; + config.arbitrum.token = undefined; + expect(WarpRouteDeployConfigSchema.safeParse(config).success).to.be.true; + }); + + it('should succeed if non-collateral type, token is empty, metadata is defined', async () => { + delete config.arbitrum.token; + config.arbitrum.totalSupply = '0'; + config.arbitrum.name = 'name'; + for (const type of NON_COLLATERAL_TYPES) { - const config: any = { - arbitrum: { - type, - mailbox: SOME_ADDRESS, - name: 'Arby Coin', - symbol: 'ARBY', - totalSupply: '10000', - }, - }; + config.arbitrum.type = type; + config.arbitrum.symbol = undefined; + expect(WarpRouteDeployConfigSchema.safeParse(config).success).to.be.false; + + config.arbitrum.symbol = 'symbol'; expect(WarpRouteDeployConfigSchema.safeParse(config).success).to.be.true; } }); diff --git a/typescript/sdk/src/token/schemas.ts b/typescript/sdk/src/token/schemas.ts index 2890261a5..8ce070c1e 100644 --- a/typescript/sdk/src/token/schemas.ts +++ b/typescript/sdk/src/token/schemas.ts @@ -1,6 +1,7 @@ import { z } from 'zod'; -import { routerConfigSchema } from '../router/schemas.js'; +import { GasRouterConfigSchema } from '../router/schemas.js'; +import { isCompliant } from '../utils/schemas.js'; import { TokenType } from './config.js'; @@ -8,49 +9,37 @@ export const TokenMetadataSchema = z.object({ name: z.string(), symbol: z.string(), totalSupply: z.string().or(z.number()), -}); - -export const TokenDecimalsSchema = z.object({ - decimals: z.number(), + decimals: z.number().optional(), scale: z.number().optional(), -}); - -export const ERC20MetadataSchema = - TokenMetadataSchema.merge(TokenDecimalsSchema).partial(); - -export const ERC721MetadataSchema = z.object({ isNft: z.boolean().optional(), }); -export const CollateralConfigSchema = ERC721MetadataSchema.merge( - ERC20MetadataSchema, -).merge( - z.object({ - type: z.enum([ - TokenType.collateral, - TokenType.collateralUri, - TokenType.fastCollateral, - TokenType.collateralVault, - ]), - token: z.string(), - }), -); +export const CollateralConfigSchema = TokenMetadataSchema.partial().extend({ + type: z.enum([ + TokenType.collateral, + TokenType.collateralVault, + TokenType.XERC20, + TokenType.XERC20Lockbox, + TokenType.collateralFiat, + TokenType.fastCollateral, + TokenType.collateralUri, + ]), + token: z + .string() + .describe('Existing token address to extend with Warp Route functionality'), +}); -export const NativeConfigSchema = TokenDecimalsSchema.partial().merge( - z.object({ - type: z.enum([TokenType.native]), - }), -); +export const NativeConfigSchema = TokenMetadataSchema.partial().extend({ + type: z.enum([TokenType.native, TokenType.nativeScaled]), +}); -export const SyntheticConfigSchema = TokenMetadataSchema.partial().merge( - z.object({ - type: z.enum([ - TokenType.synthetic, - TokenType.syntheticUri, - TokenType.fastSynthetic, - ]), - }), -); +export const SyntheticConfigSchema = TokenMetadataSchema.partial().extend({ + type: z.enum([ + TokenType.synthetic, + TokenType.syntheticUri, + TokenType.fastSynthetic, + ]), +}); /** * @remarks @@ -63,12 +52,26 @@ export const TokenConfigSchema = z.discriminatedUnion('type', [ SyntheticConfigSchema, ]); -export const TokenRouterConfigSchema = z.intersection( - TokenConfigSchema, - routerConfigSchema, +export const TokenRouterConfigSchema = TokenConfigSchema.and( + GasRouterConfigSchema, ); -export const WarpRouteDeployConfigSchema = z.record( - z.string(), - TokenRouterConfigSchema, -); +export type TokenRouterConfig = z.infer; +export type NativeConfig = z.infer; +export type CollateralConfig = z.infer; + +export const isSyntheticConfig = isCompliant(SyntheticConfigSchema); +export const isCollateralConfig = isCompliant(CollateralConfigSchema); +export const isNativeConfig = isCompliant(NativeConfigSchema); +export const isTokenMetadata = isCompliant(TokenMetadataSchema); + +export const WarpRouteDeployConfigSchema = z + .record(TokenRouterConfigSchema) + .refine((configMap) => { + const entries = Object.entries(configMap); + return ( + entries.some( + ([_, config]) => isCollateralConfig(config) || isNativeConfig(config), + ) || entries.every(([_, config]) => isTokenMetadata(config)) + ); + }, `Config must include Native or Collateral OR all synthetics must define token metadata`); diff --git a/typescript/sdk/src/token/types.ts b/typescript/sdk/src/token/types.ts index 0d0a70800..8cbead331 100644 --- a/typescript/sdk/src/token/types.ts +++ b/typescript/sdk/src/token/types.ts @@ -1,9 +1,11 @@ import { z } from 'zod'; import { + TokenMetadataSchema, TokenRouterConfigSchema, WarpRouteDeployConfigSchema, } from './schemas.js'; +export type TokenMetadata = z.infer; export type TokenRouterConfig = z.infer; export type WarpRouteDeployConfig = z.infer; diff --git a/typescript/sdk/src/utils/schemas.ts b/typescript/sdk/src/utils/schemas.ts new file mode 100644 index 000000000..2babea6c0 --- /dev/null +++ b/typescript/sdk/src/utils/schemas.ts @@ -0,0 +1,6 @@ +import { z } from 'zod'; + +export function isCompliant(schema: S) { + return (config: unknown): config is z.infer => + schema.safeParse(config).success; +} diff --git a/typescript/utils/CHANGELOG.md b/typescript/utils/CHANGELOG.md index f72911518..0dce2d1b6 100644 --- a/typescript/utils/CHANGELOG.md +++ b/typescript/utils/CHANGELOG.md @@ -1,5 +1,11 @@ # @hyperlane-xyz/utils +## 3.13.0 + +### Minor Changes + +- 0cf692e73: Implement metadata builder fetching from message + ## 3.12.0 ### Minor Changes diff --git a/typescript/utils/package.json b/typescript/utils/package.json index 094b5b053..260348910 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.12.2", + "version": "3.13.0", "dependencies": { "@cosmjs/encoding": "^0.31.3", "@solana/web3.js": "^1.78.0", diff --git a/typescript/utils/src/arrays.ts b/typescript/utils/src/arrays.ts index 25eb1383e..9f8c67ed2 100644 --- a/typescript/utils/src/arrays.ts +++ b/typescript/utils/src/arrays.ts @@ -1,3 +1,5 @@ +import { randomInt } from './math.js'; + interface Sliceable { length: number; slice: (i: number, j: number) => any; @@ -14,3 +16,7 @@ export function chunk(str: T, size: number) { export function exclude(item: T, list: T[]) { return list.filter((i) => i !== item); } + +export function randomElement(list: T[]) { + return list[randomInt(list.length)]; +} diff --git a/typescript/utils/src/index.ts b/typescript/utils/src/index.ts index 08b85a1c8..fdaa20476 100644 --- a/typescript/utils/src/index.ts +++ b/typescript/utils/src/index.ts @@ -46,7 +46,7 @@ export { toWei, tryParseAmount, } from './amount.js'; -export { chunk, exclude } from './arrays.js'; +export { chunk, exclude, randomElement } from './arrays.js'; export { concurrentMap, pollAsync, @@ -88,7 +88,7 @@ export { rootLogger, setRootLogger, } from './logging.js'; -export { mean, median, stdDev, sum } from './math.js'; +export { mean, median, randomInt, stdDev, sum } from './math.js'; export { formatMessage, messageId, parseMessage } from './messages.js'; export { formatLegacyMultisigIsmMetadata, @@ -115,25 +115,26 @@ export { export { difference, setEquality, symmetricDifference } from './sets.js'; export { errorToString, + fromHexString, sanitizeString, streamToString, + toHexString, toTitleCase, trimToLength, - fromHexString, - toHexString, } from './strings.js'; export { isNullish, isNumeric } from './typeof.js'; export { Address, AddressBytes32, Annotated, + Announcement, CallData, ChainCaip2Id, ChainId, Checkpoint, + CheckpointWithId, Domain, HexString, - InterchainSecurityModuleType, MerkleProof, MessageStatus, Numberish, @@ -142,6 +143,7 @@ export { ProtocolSmallestUnit, ProtocolType, ProtocolTypeValue, + S3Announcement, S3Checkpoint, S3CheckpointWithId, SignatureLike, @@ -150,4 +152,4 @@ export { } from './types.js'; export { isHttpsUrl } from './url.js'; export { assert } from './validation.js'; -export { BaseValidator } from './validator.js'; +export { BaseValidator, ValidatorConfig } from './validator.js'; diff --git a/typescript/utils/src/math.ts b/typescript/utils/src/math.ts index d1a3bb910..ebca6e75e 100644 --- a/typescript/utils/src/math.ts +++ b/typescript/utils/src/math.ts @@ -19,3 +19,7 @@ export function stdDev(a: number[]): number { const squaredDifferences = a.map((x) => Math.pow(x - xbar, 2)); return Math.sqrt(mean(squaredDifferences)); } + +export function randomInt(max: number, min = 0): number { + return Math.floor(Math.random() * (max - min)) + min; +} diff --git a/typescript/utils/src/objects.ts b/typescript/utils/src/objects.ts index 21e122d86..131ab1f4b 100644 --- a/typescript/utils/src/objects.ts +++ b/typescript/utils/src/objects.ts @@ -1,6 +1,7 @@ import { stringify as yamlStringify } from 'yaml'; import { ethersBigNumberSerializer } from './logging.js'; +import { assert } from './validation.js'; export function isObject(item: any) { return item && typeof item === 'object' && !Array.isArray(item); @@ -57,6 +58,23 @@ export function objFilter( ) as Record; } +export function deepFind( + obj: I, + func: (v: I) => v is O, + depth = 10, +): O | undefined { + assert(depth > 0, 'deepFind max depth reached'); + if (func(obj)) { + return obj; + } + const entries = isObject(obj) + ? Object.values(obj) + : Array.isArray(obj) + ? obj + : []; + return entries.map((e) => deepFind(e as any, func, depth - 1)).find((v) => v); +} + // promiseObjectAll :: {k: Promise a} -> Promise {k: a} export function promiseObjAll(obj: { [key in K]: Promise; diff --git a/typescript/utils/src/types.ts b/typescript/utils/src/types.ts index 55e3c4600..122a4f82a 100644 --- a/typescript/utils/src/types.ts +++ b/typescript/utils/src/types.ts @@ -1,3 +1,4 @@ +import type { SignatureLike } from '@ethersproject/bytes'; import type { BigNumber, ethers } from 'ethers'; export enum ProtocolType { @@ -28,17 +29,6 @@ export type WithAddress = T & { address: Address; }; -// copied from node_modules/@ethersproject/bytes/src.ts/index.ts -export type SignatureLike = - | { - r: string; - s?: string; - _vs?: string; - recoveryParam?: number; - v?: number; - } - | ethers.utils.BytesLike; - export type MerkleProof = { branch: ethers.utils.BytesLike[]; leaf: ethers.utils.BytesLike; @@ -46,6 +36,13 @@ export type MerkleProof = { }; /********* HYPERLANE CORE *********/ +export type Announcement = { + mailbox_domain: Domain; + mailbox_address: Address; + validator: Address; + storage_location: string; +}; + export type Checkpoint = { root: string; index: number; // safe because 2 ** 32 leaves < Number.MAX_VALUE @@ -53,14 +50,23 @@ export type Checkpoint = { merkle_tree_hook_address: Address; }; +export type CheckpointWithId = { + checkpoint: Checkpoint; + message_id: HexString; +}; + +export { SignatureLike }; + /** * Shape of a checkpoint in S3 as published by the agent. */ export type S3CheckpointWithId = { - value: { - checkpoint: Checkpoint; - message_id: HexString; - }; + value: CheckpointWithId; + signature: SignatureLike; +}; + +export type S3Announcement = { + value: Announcement; signature: SignatureLike; }; @@ -84,8 +90,10 @@ export type ParsedMessage = { version: number; nonce: number; origin: number; + originChain?: string; sender: string; destination: number; + destinationChain?: string; recipient: string; body: string; }; @@ -99,10 +107,6 @@ export type ParsedLegacyMultisigIsmMetadata = { validators: ethers.utils.BytesLike[]; }; -export enum InterchainSecurityModuleType { - MULTISIG = 3, -} - export type Annotated = T & { annotation?: string; }; diff --git a/typescript/utils/src/validator.ts b/typescript/utils/src/validator.ts index e651fafa6..99cb9772e 100644 --- a/typescript/utils/src/validator.ts +++ b/typescript/utils/src/validator.ts @@ -1,36 +1,50 @@ import { ethers } from 'ethers'; +import { eqAddress } from './addresses.js'; import { domainHash } from './domains.js'; import { Address, Checkpoint, - Domain, + CheckpointWithId, HexString, + S3CheckpointWithId, SignatureLike, } from './types.js'; +export interface ValidatorConfig { + address: string; + localDomain: number; + mailbox: string; +} + /** * Utilities for validators to construct and verify checkpoints. */ export class BaseValidator { - constructor( - public readonly address: Address, - public readonly localDomain: Domain, - public readonly mailbox_address: Address, - ) {} + constructor(protected readonly config: ValidatorConfig) {} + + get address() { + return this.config.address; + } announceDomainHash() { - return domainHash(this.localDomain, this.mailbox_address); + return domainHash(this.config.localDomain, this.config.mailbox); } - checkpointDomainHash(merkle_tree_address: Address) { - return domainHash(this.localDomain, merkle_tree_address); + static checkpointDomainHash( + localDomain: number, + merkle_tree_address: Address, + ) { + return domainHash(localDomain, merkle_tree_address); } - message(checkpoint: Checkpoint, messageId: HexString) { + static message(checkpoint: Checkpoint, messageId: HexString) { const types = ['bytes32', 'bytes32', 'uint32', 'bytes32']; const values = [ - this.checkpointDomainHash(checkpoint.merkle_tree_hook_address), + this.checkpointDomainHash( + checkpoint.mailbox_domain, + checkpoint.merkle_tree_hook_address, + ), checkpoint.root, checkpoint.index, messageId, @@ -38,12 +52,12 @@ export class BaseValidator { return ethers.utils.solidityPack(types, values); } - messageHash(checkpoint: Checkpoint, messageId: HexString) { + static messageHash(checkpoint: Checkpoint, messageId: HexString) { const message = this.message(checkpoint, messageId); return ethers.utils.arrayify(ethers.utils.keccak256(message)); } - recoverAddressFromCheckpoint( + static recoverAddressFromCheckpoint( checkpoint: Checkpoint, signature: SignatureLike, messageId: HexString, @@ -52,17 +66,31 @@ export class BaseValidator { return ethers.utils.verifyMessage(msgHash, signature); } + static recoverAddressFromCheckpointWithId( + { checkpoint, message_id }: CheckpointWithId, + signature: SignatureLike, + ): Address { + return BaseValidator.recoverAddressFromCheckpoint( + checkpoint, + signature, + message_id, + ); + } + + static recoverAddress({ value, signature }: S3CheckpointWithId): Address { + return BaseValidator.recoverAddressFromCheckpointWithId(value, signature); + } + matchesSigner( checkpoint: Checkpoint, signature: SignatureLike, messageId: HexString, ) { - return ( - this.recoverAddressFromCheckpoint( - checkpoint, - signature, - messageId, - ).toLowerCase() === this.address.toLowerCase() + const address = BaseValidator.recoverAddressFromCheckpoint( + checkpoint, + signature, + messageId, ); + return eqAddress(address, this.config.address); } } diff --git a/yarn.lock b/yarn.lock index 2adc97823..9fc2aab50 100644 --- a/yarn.lock +++ b/yarn.lock @@ -52,6 +52,17 @@ __metadata: languageName: node linkType: hard +"@aws-crypto/crc32@npm:3.0.0": + version: 3.0.0 + resolution: "@aws-crypto/crc32@npm:3.0.0" + dependencies: + "@aws-crypto/util": "npm:^3.0.0" + "@aws-sdk/types": "npm:^3.222.0" + tslib: "npm:^1.11.1" + checksum: 672d593fd98a88709a1b488db92aabf584b6dad3e8099e04b6d2870e34a2ee668cbbe0e5406e60c0d776b9c34a91cfc427999230ad959518fed56a3db037704c + languageName: node + linkType: hard + "@aws-crypto/crc32c@npm:2.0.0": version: 2.0.0 resolution: "@aws-crypto/crc32c@npm:2.0.0" @@ -63,6 +74,17 @@ __metadata: languageName: node linkType: hard +"@aws-crypto/crc32c@npm:3.0.0": + version: 3.0.0 + resolution: "@aws-crypto/crc32c@npm:3.0.0" + dependencies: + "@aws-crypto/util": "npm:^3.0.0" + "@aws-sdk/types": "npm:^3.222.0" + tslib: "npm:^1.11.1" + checksum: 3e604ad7a8d3fb10e5fe11597d593d0ae8e1d6dc06a06b8d882d5732a6e181f6a77fd4f92fb3ae9002a2007121d49e40bc6b78d83af62d36deb1b457b7f1d977 + languageName: node + linkType: hard + "@aws-crypto/ie11-detection@npm:^2.0.0": version: 2.0.0 resolution: "@aws-crypto/ie11-detection@npm:2.0.0" @@ -72,6 +94,15 @@ __metadata: languageName: node linkType: hard +"@aws-crypto/ie11-detection@npm:^3.0.0": + version: 3.0.0 + resolution: "@aws-crypto/ie11-detection@npm:3.0.0" + dependencies: + tslib: "npm:^1.11.1" + checksum: f5aee4a11a113ab9640474e75d398c99538aa30775f484cd519f0de0096ae0d4a6b68d2f0c685f24bd6f2425067c565bc20592c36c0dc1f4d28c1b4751a40734 + languageName: node + linkType: hard + "@aws-crypto/sha1-browser@npm:2.0.0": version: 2.0.0 resolution: "@aws-crypto/sha1-browser@npm:2.0.0" @@ -86,6 +117,21 @@ __metadata: languageName: node linkType: hard +"@aws-crypto/sha1-browser@npm:3.0.0": + version: 3.0.0 + resolution: "@aws-crypto/sha1-browser@npm:3.0.0" + dependencies: + "@aws-crypto/ie11-detection": "npm:^3.0.0" + "@aws-crypto/supports-web-crypto": "npm:^3.0.0" + "@aws-crypto/util": "npm:^3.0.0" + "@aws-sdk/types": "npm:^3.222.0" + "@aws-sdk/util-locate-window": "npm:^3.0.0" + "@aws-sdk/util-utf8-browser": "npm:^3.0.0" + tslib: "npm:^1.11.1" + checksum: 8c30fa1e427bf2c295077b007835b0dd9af6beb6250e0aa775cecd42a1f517ef211751e7e12c2423f39d9b1c6748b99eb7b73207eb69165abc696cc470d8659e + languageName: node + linkType: hard + "@aws-crypto/sha256-browser@npm:2.0.0": version: 2.0.0 resolution: "@aws-crypto/sha256-browser@npm:2.0.0" @@ -102,6 +148,22 @@ __metadata: languageName: node linkType: hard +"@aws-crypto/sha256-browser@npm:3.0.0": + version: 3.0.0 + resolution: "@aws-crypto/sha256-browser@npm:3.0.0" + dependencies: + "@aws-crypto/ie11-detection": "npm:^3.0.0" + "@aws-crypto/sha256-js": "npm:^3.0.0" + "@aws-crypto/supports-web-crypto": "npm:^3.0.0" + "@aws-crypto/util": "npm:^3.0.0" + "@aws-sdk/types": "npm:^3.222.0" + "@aws-sdk/util-locate-window": "npm:^3.0.0" + "@aws-sdk/util-utf8-browser": "npm:^3.0.0" + tslib: "npm:^1.11.1" + checksum: 4e075906c48a46bbb8babb60db3e6b280db405a88c68b77c1496c26218292d5ea509beae3ccc19366ca6bc944c6d37fe347d0917909900dbac86f054a19c71c7 + languageName: node + linkType: hard + "@aws-crypto/sha256-js@npm:1.2.2": version: 1.2.2 resolution: "@aws-crypto/sha256-js@npm:1.2.2" @@ -124,6 +186,17 @@ __metadata: languageName: node linkType: hard +"@aws-crypto/sha256-js@npm:3.0.0, @aws-crypto/sha256-js@npm:^3.0.0": + version: 3.0.0 + resolution: "@aws-crypto/sha256-js@npm:3.0.0" + dependencies: + "@aws-crypto/util": "npm:^3.0.0" + "@aws-sdk/types": "npm:^3.222.0" + tslib: "npm:^1.11.1" + checksum: f9fc2d51631950434d0f91f51c2ce17845d4e8e75971806e21604987e3186ee1e54de8a89e5349585b91cb36e56d5f058d6a45004e1bfbce1351dbb40f479152 + languageName: node + linkType: hard + "@aws-crypto/sha256-js@npm:^2.0.0": version: 2.0.1 resolution: "@aws-crypto/sha256-js@npm:2.0.1" @@ -144,6 +217,15 @@ __metadata: languageName: node linkType: hard +"@aws-crypto/supports-web-crypto@npm:^3.0.0": + version: 3.0.0 + resolution: "@aws-crypto/supports-web-crypto@npm:3.0.0" + dependencies: + tslib: "npm:^1.11.1" + checksum: 8a48788d2866e391354f256aa79b577b2ba1474b50184cbe690467de7e64a79928afece95007ab69a1556f99da97ea129487db091d94489847e14decdc7c9a6f + languageName: node + linkType: hard + "@aws-crypto/util@npm:^1.2.2": version: 1.2.2 resolution: "@aws-crypto/util@npm:1.2.2" @@ -166,6 +248,17 @@ __metadata: languageName: node linkType: hard +"@aws-crypto/util@npm:^3.0.0": + version: 3.0.0 + resolution: "@aws-crypto/util@npm:3.0.0" + dependencies: + "@aws-sdk/types": "npm:^3.222.0" + "@aws-sdk/util-utf8-browser": "npm:^3.0.0" + tslib: "npm:^1.11.1" + checksum: 92c835b83d7a888b37b2f2a37c82e58bb8fabb617e371173c488d2a71b916c69ee566f0ea0b3f7f4e16296226c49793f95b3d59fc07a7ca00af91f8f9f29e6c4 + languageName: node + linkType: hard + "@aws-sdk/abort-controller@npm:3.127.0": version: 3.127.0 resolution: "@aws-sdk/abort-controller@npm:3.127.0" @@ -343,6 +436,121 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/client-kms@npm:^3.577.0": + version: 3.577.0 + resolution: "@aws-sdk/client-kms@npm:3.577.0" + dependencies: + "@aws-crypto/sha256-browser": "npm:3.0.0" + "@aws-crypto/sha256-js": "npm:3.0.0" + "@aws-sdk/client-sso-oidc": "npm:3.577.0" + "@aws-sdk/client-sts": "npm:3.577.0" + "@aws-sdk/core": "npm:3.576.0" + "@aws-sdk/credential-provider-node": "npm:3.577.0" + "@aws-sdk/middleware-host-header": "npm:3.577.0" + "@aws-sdk/middleware-logger": "npm:3.577.0" + "@aws-sdk/middleware-recursion-detection": "npm:3.577.0" + "@aws-sdk/middleware-user-agent": "npm:3.577.0" + "@aws-sdk/region-config-resolver": "npm:3.577.0" + "@aws-sdk/types": "npm:3.577.0" + "@aws-sdk/util-endpoints": "npm:3.577.0" + "@aws-sdk/util-user-agent-browser": "npm:3.577.0" + "@aws-sdk/util-user-agent-node": "npm:3.577.0" + "@smithy/config-resolver": "npm:^3.0.0" + "@smithy/core": "npm:^2.0.0" + "@smithy/fetch-http-handler": "npm:^3.0.0" + "@smithy/hash-node": "npm:^3.0.0" + "@smithy/invalid-dependency": "npm:^3.0.0" + "@smithy/middleware-content-length": "npm:^3.0.0" + "@smithy/middleware-endpoint": "npm:^3.0.0" + "@smithy/middleware-retry": "npm:^3.0.0" + "@smithy/middleware-serde": "npm:^3.0.0" + "@smithy/middleware-stack": "npm:^3.0.0" + "@smithy/node-config-provider": "npm:^3.0.0" + "@smithy/node-http-handler": "npm:^3.0.0" + "@smithy/protocol-http": "npm:^4.0.0" + "@smithy/smithy-client": "npm:^3.0.0" + "@smithy/types": "npm:^3.0.0" + "@smithy/url-parser": "npm:^3.0.0" + "@smithy/util-base64": "npm:^3.0.0" + "@smithy/util-body-length-browser": "npm:^3.0.0" + "@smithy/util-body-length-node": "npm:^3.0.0" + "@smithy/util-defaults-mode-browser": "npm:^3.0.0" + "@smithy/util-defaults-mode-node": "npm:^3.0.0" + "@smithy/util-endpoints": "npm:^2.0.0" + "@smithy/util-middleware": "npm:^3.0.0" + "@smithy/util-retry": "npm:^3.0.0" + "@smithy/util-utf8": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: 2d4e4bb8c05e711c588cca4c7fa2737ffcb0f778c013cb97093b9b8d71b7e0e6a67d3354f50a0d230e2c0d54abcf7c19288c4750c71b734bd0b01f24d816ebcf + languageName: node + linkType: hard + +"@aws-sdk/client-s3@npm:^3.577.0": + version: 3.577.0 + resolution: "@aws-sdk/client-s3@npm:3.577.0" + dependencies: + "@aws-crypto/sha1-browser": "npm:3.0.0" + "@aws-crypto/sha256-browser": "npm:3.0.0" + "@aws-crypto/sha256-js": "npm:3.0.0" + "@aws-sdk/client-sso-oidc": "npm:3.577.0" + "@aws-sdk/client-sts": "npm:3.577.0" + "@aws-sdk/core": "npm:3.576.0" + "@aws-sdk/credential-provider-node": "npm:3.577.0" + "@aws-sdk/middleware-bucket-endpoint": "npm:3.577.0" + "@aws-sdk/middleware-expect-continue": "npm:3.577.0" + "@aws-sdk/middleware-flexible-checksums": "npm:3.577.0" + "@aws-sdk/middleware-host-header": "npm:3.577.0" + "@aws-sdk/middleware-location-constraint": "npm:3.577.0" + "@aws-sdk/middleware-logger": "npm:3.577.0" + "@aws-sdk/middleware-recursion-detection": "npm:3.577.0" + "@aws-sdk/middleware-sdk-s3": "npm:3.577.0" + "@aws-sdk/middleware-signing": "npm:3.577.0" + "@aws-sdk/middleware-ssec": "npm:3.577.0" + "@aws-sdk/middleware-user-agent": "npm:3.577.0" + "@aws-sdk/region-config-resolver": "npm:3.577.0" + "@aws-sdk/signature-v4-multi-region": "npm:3.577.0" + "@aws-sdk/types": "npm:3.577.0" + "@aws-sdk/util-endpoints": "npm:3.577.0" + "@aws-sdk/util-user-agent-browser": "npm:3.577.0" + "@aws-sdk/util-user-agent-node": "npm:3.577.0" + "@aws-sdk/xml-builder": "npm:3.575.0" + "@smithy/config-resolver": "npm:^3.0.0" + "@smithy/core": "npm:^2.0.0" + "@smithy/eventstream-serde-browser": "npm:^3.0.0" + "@smithy/eventstream-serde-config-resolver": "npm:^3.0.0" + "@smithy/eventstream-serde-node": "npm:^3.0.0" + "@smithy/fetch-http-handler": "npm:^3.0.0" + "@smithy/hash-blob-browser": "npm:^3.0.0" + "@smithy/hash-node": "npm:^3.0.0" + "@smithy/hash-stream-node": "npm:^3.0.0" + "@smithy/invalid-dependency": "npm:^3.0.0" + "@smithy/md5-js": "npm:^3.0.0" + "@smithy/middleware-content-length": "npm:^3.0.0" + "@smithy/middleware-endpoint": "npm:^3.0.0" + "@smithy/middleware-retry": "npm:^3.0.0" + "@smithy/middleware-serde": "npm:^3.0.0" + "@smithy/middleware-stack": "npm:^3.0.0" + "@smithy/node-config-provider": "npm:^3.0.0" + "@smithy/node-http-handler": "npm:^3.0.0" + "@smithy/protocol-http": "npm:^4.0.0" + "@smithy/smithy-client": "npm:^3.0.0" + "@smithy/types": "npm:^3.0.0" + "@smithy/url-parser": "npm:^3.0.0" + "@smithy/util-base64": "npm:^3.0.0" + "@smithy/util-body-length-browser": "npm:^3.0.0" + "@smithy/util-body-length-node": "npm:^3.0.0" + "@smithy/util-defaults-mode-browser": "npm:^3.0.0" + "@smithy/util-defaults-mode-node": "npm:^3.0.0" + "@smithy/util-endpoints": "npm:^2.0.0" + "@smithy/util-retry": "npm:^3.0.0" + "@smithy/util-stream": "npm:^3.0.0" + "@smithy/util-utf8": "npm:^3.0.0" + "@smithy/util-waiter": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: 0b52b3cec27544b8c528ad63d9c80803ba7bae4b0336028faee1e30d8e45ce9f0832b381cc2cf8f122a017c20a2ca8c8b20365b42ec6c6b27b9b58df36d96279 + languageName: node + linkType: hard + "@aws-sdk/client-s3@npm:^3.74.0": version: 3.107.0 resolution: "@aws-sdk/client-s3@npm:3.107.0" @@ -405,6 +613,54 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/client-sso-oidc@npm:3.577.0": + version: 3.577.0 + resolution: "@aws-sdk/client-sso-oidc@npm:3.577.0" + dependencies: + "@aws-crypto/sha256-browser": "npm:3.0.0" + "@aws-crypto/sha256-js": "npm:3.0.0" + "@aws-sdk/client-sts": "npm:3.577.0" + "@aws-sdk/core": "npm:3.576.0" + "@aws-sdk/credential-provider-node": "npm:3.577.0" + "@aws-sdk/middleware-host-header": "npm:3.577.0" + "@aws-sdk/middleware-logger": "npm:3.577.0" + "@aws-sdk/middleware-recursion-detection": "npm:3.577.0" + "@aws-sdk/middleware-user-agent": "npm:3.577.0" + "@aws-sdk/region-config-resolver": "npm:3.577.0" + "@aws-sdk/types": "npm:3.577.0" + "@aws-sdk/util-endpoints": "npm:3.577.0" + "@aws-sdk/util-user-agent-browser": "npm:3.577.0" + "@aws-sdk/util-user-agent-node": "npm:3.577.0" + "@smithy/config-resolver": "npm:^3.0.0" + "@smithy/core": "npm:^2.0.0" + "@smithy/fetch-http-handler": "npm:^3.0.0" + "@smithy/hash-node": "npm:^3.0.0" + "@smithy/invalid-dependency": "npm:^3.0.0" + "@smithy/middleware-content-length": "npm:^3.0.0" + "@smithy/middleware-endpoint": "npm:^3.0.0" + "@smithy/middleware-retry": "npm:^3.0.0" + "@smithy/middleware-serde": "npm:^3.0.0" + "@smithy/middleware-stack": "npm:^3.0.0" + "@smithy/node-config-provider": "npm:^3.0.0" + "@smithy/node-http-handler": "npm:^3.0.0" + "@smithy/protocol-http": "npm:^4.0.0" + "@smithy/smithy-client": "npm:^3.0.0" + "@smithy/types": "npm:^3.0.0" + "@smithy/url-parser": "npm:^3.0.0" + "@smithy/util-base64": "npm:^3.0.0" + "@smithy/util-body-length-browser": "npm:^3.0.0" + "@smithy/util-body-length-node": "npm:^3.0.0" + "@smithy/util-defaults-mode-browser": "npm:^3.0.0" + "@smithy/util-defaults-mode-node": "npm:^3.0.0" + "@smithy/util-endpoints": "npm:^2.0.0" + "@smithy/util-middleware": "npm:^3.0.0" + "@smithy/util-retry": "npm:^3.0.0" + "@smithy/util-utf8": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: 498ae3ed9b7b5ff5c2d5312f24e5f58eb7e64f41184958ba7fda907c9277807790f4299f0622374c4372c23bf7967b2fb9d8a73495e7503cf4ee2fc9dc711e43 + languageName: node + linkType: hard + "@aws-sdk/client-sso@npm:3.105.0": version: 3.105.0 resolution: "@aws-sdk/client-sso@npm:3.105.0" @@ -521,6 +777,52 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/client-sso@npm:3.577.0": + version: 3.577.0 + resolution: "@aws-sdk/client-sso@npm:3.577.0" + dependencies: + "@aws-crypto/sha256-browser": "npm:3.0.0" + "@aws-crypto/sha256-js": "npm:3.0.0" + "@aws-sdk/core": "npm:3.576.0" + "@aws-sdk/middleware-host-header": "npm:3.577.0" + "@aws-sdk/middleware-logger": "npm:3.577.0" + "@aws-sdk/middleware-recursion-detection": "npm:3.577.0" + "@aws-sdk/middleware-user-agent": "npm:3.577.0" + "@aws-sdk/region-config-resolver": "npm:3.577.0" + "@aws-sdk/types": "npm:3.577.0" + "@aws-sdk/util-endpoints": "npm:3.577.0" + "@aws-sdk/util-user-agent-browser": "npm:3.577.0" + "@aws-sdk/util-user-agent-node": "npm:3.577.0" + "@smithy/config-resolver": "npm:^3.0.0" + "@smithy/core": "npm:^2.0.0" + "@smithy/fetch-http-handler": "npm:^3.0.0" + "@smithy/hash-node": "npm:^3.0.0" + "@smithy/invalid-dependency": "npm:^3.0.0" + "@smithy/middleware-content-length": "npm:^3.0.0" + "@smithy/middleware-endpoint": "npm:^3.0.0" + "@smithy/middleware-retry": "npm:^3.0.0" + "@smithy/middleware-serde": "npm:^3.0.0" + "@smithy/middleware-stack": "npm:^3.0.0" + "@smithy/node-config-provider": "npm:^3.0.0" + "@smithy/node-http-handler": "npm:^3.0.0" + "@smithy/protocol-http": "npm:^4.0.0" + "@smithy/smithy-client": "npm:^3.0.0" + "@smithy/types": "npm:^3.0.0" + "@smithy/url-parser": "npm:^3.0.0" + "@smithy/util-base64": "npm:^3.0.0" + "@smithy/util-body-length-browser": "npm:^3.0.0" + "@smithy/util-body-length-node": "npm:^3.0.0" + "@smithy/util-defaults-mode-browser": "npm:^3.0.0" + "@smithy/util-defaults-mode-node": "npm:^3.0.0" + "@smithy/util-endpoints": "npm:^2.0.0" + "@smithy/util-middleware": "npm:^3.0.0" + "@smithy/util-retry": "npm:^3.0.0" + "@smithy/util-utf8": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: c7a6786e469d278d86579f2c74e334e4aac038becc881a60fb30fd175495e46ee4f41ed0d011445fc312bebcd2754531482180aa7b505d0768cdb472b3e4f633 + languageName: node + linkType: hard + "@aws-sdk/client-sts@npm:3.105.0": version: 3.105.0 resolution: "@aws-sdk/client-sts@npm:3.105.0" @@ -652,6 +954,54 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/client-sts@npm:3.577.0": + version: 3.577.0 + resolution: "@aws-sdk/client-sts@npm:3.577.0" + dependencies: + "@aws-crypto/sha256-browser": "npm:3.0.0" + "@aws-crypto/sha256-js": "npm:3.0.0" + "@aws-sdk/client-sso-oidc": "npm:3.577.0" + "@aws-sdk/core": "npm:3.576.0" + "@aws-sdk/credential-provider-node": "npm:3.577.0" + "@aws-sdk/middleware-host-header": "npm:3.577.0" + "@aws-sdk/middleware-logger": "npm:3.577.0" + "@aws-sdk/middleware-recursion-detection": "npm:3.577.0" + "@aws-sdk/middleware-user-agent": "npm:3.577.0" + "@aws-sdk/region-config-resolver": "npm:3.577.0" + "@aws-sdk/types": "npm:3.577.0" + "@aws-sdk/util-endpoints": "npm:3.577.0" + "@aws-sdk/util-user-agent-browser": "npm:3.577.0" + "@aws-sdk/util-user-agent-node": "npm:3.577.0" + "@smithy/config-resolver": "npm:^3.0.0" + "@smithy/core": "npm:^2.0.0" + "@smithy/fetch-http-handler": "npm:^3.0.0" + "@smithy/hash-node": "npm:^3.0.0" + "@smithy/invalid-dependency": "npm:^3.0.0" + "@smithy/middleware-content-length": "npm:^3.0.0" + "@smithy/middleware-endpoint": "npm:^3.0.0" + "@smithy/middleware-retry": "npm:^3.0.0" + "@smithy/middleware-serde": "npm:^3.0.0" + "@smithy/middleware-stack": "npm:^3.0.0" + "@smithy/node-config-provider": "npm:^3.0.0" + "@smithy/node-http-handler": "npm:^3.0.0" + "@smithy/protocol-http": "npm:^4.0.0" + "@smithy/smithy-client": "npm:^3.0.0" + "@smithy/types": "npm:^3.0.0" + "@smithy/url-parser": "npm:^3.0.0" + "@smithy/util-base64": "npm:^3.0.0" + "@smithy/util-body-length-browser": "npm:^3.0.0" + "@smithy/util-body-length-node": "npm:^3.0.0" + "@smithy/util-defaults-mode-browser": "npm:^3.0.0" + "@smithy/util-defaults-mode-node": "npm:^3.0.0" + "@smithy/util-endpoints": "npm:^2.0.0" + "@smithy/util-middleware": "npm:^3.0.0" + "@smithy/util-retry": "npm:^3.0.0" + "@smithy/util-utf8": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: 7eb6a1b463d4173295f22dbe496c046bc32e5f9fc5bafbe02975d0dd215c46050e40a3251fd92fff16024abba4c1ae5f1da3e82933b944d1d21eadb315037201 + languageName: node + linkType: hard + "@aws-sdk/config-resolver@npm:3.130.0": version: 3.130.0 resolution: "@aws-sdk/config-resolver@npm:3.130.0" @@ -690,6 +1040,21 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/core@npm:3.576.0": + version: 3.576.0 + resolution: "@aws-sdk/core@npm:3.576.0" + dependencies: + "@smithy/core": "npm:^2.0.0" + "@smithy/protocol-http": "npm:^4.0.0" + "@smithy/signature-v4": "npm:^3.0.0" + "@smithy/smithy-client": "npm:^3.0.0" + "@smithy/types": "npm:^3.0.0" + fast-xml-parser: "npm:4.2.5" + tslib: "npm:^2.6.2" + checksum: 9bf9d28b380a929c1f8be0c67f61529a5d3b21815b5a0c0e22608767ee8d420a1d81c461f17f60af98f09a1600a4fe105e411b0b56d132c69fb831f0f9f5fdfe + languageName: node + linkType: hard + "@aws-sdk/credential-provider-env@npm:3.127.0": version: 3.127.0 resolution: "@aws-sdk/credential-provider-env@npm:3.127.0" @@ -712,6 +1077,18 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/credential-provider-env@npm:3.577.0": + version: 3.577.0 + resolution: "@aws-sdk/credential-provider-env@npm:3.577.0" + dependencies: + "@aws-sdk/types": "npm:3.577.0" + "@smithy/property-provider": "npm:^3.0.0" + "@smithy/types": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: 1fb3db7f2490661488dd4c72e55af7680782335c69534527f0185b97fb554a15f88721c25484c772f2ba498f734e6629b27dfac7d375f4ea7794cc814bc88e8e + languageName: node + linkType: hard + "@aws-sdk/credential-provider-env@npm:3.78.0": version: 3.78.0 resolution: "@aws-sdk/credential-provider-env@npm:3.78.0" @@ -723,6 +1100,23 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/credential-provider-http@npm:3.577.0": + version: 3.577.0 + resolution: "@aws-sdk/credential-provider-http@npm:3.577.0" + dependencies: + "@aws-sdk/types": "npm:3.577.0" + "@smithy/fetch-http-handler": "npm:^3.0.0" + "@smithy/node-http-handler": "npm:^3.0.0" + "@smithy/property-provider": "npm:^3.0.0" + "@smithy/protocol-http": "npm:^4.0.0" + "@smithy/smithy-client": "npm:^3.0.0" + "@smithy/types": "npm:^3.0.0" + "@smithy/util-stream": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: 55299138b472e4cc63914442b89fa7db72bbe5f4c3d631e6a63024db95239a711e242b138352b11cfcb9ccb15ede0632940e811f23b9f2ac1b9f6bb5ebe847a5 + languageName: node + linkType: hard + "@aws-sdk/credential-provider-imds@npm:3.127.0": version: 3.127.0 resolution: "@aws-sdk/credential-provider-imds@npm:3.127.0" @@ -811,6 +1205,26 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/credential-provider-ini@npm:3.577.0": + version: 3.577.0 + resolution: "@aws-sdk/credential-provider-ini@npm:3.577.0" + dependencies: + "@aws-sdk/credential-provider-env": "npm:3.577.0" + "@aws-sdk/credential-provider-process": "npm:3.577.0" + "@aws-sdk/credential-provider-sso": "npm:3.577.0" + "@aws-sdk/credential-provider-web-identity": "npm:3.577.0" + "@aws-sdk/types": "npm:3.577.0" + "@smithy/credential-provider-imds": "npm:^3.0.0" + "@smithy/property-provider": "npm:^3.0.0" + "@smithy/shared-ini-file-loader": "npm:^3.0.0" + "@smithy/types": "npm:^3.0.0" + tslib: "npm:^2.6.2" + peerDependencies: + "@aws-sdk/client-sts": ^3.577.0 + checksum: 505373cdd7577be8d5d31c11d0c35de5b9660dcfe550a92d421340c30d090d5f35575902e486146b49b2a5e8842c003e2a8aa1feb196a31074945e99e9fa9295 + languageName: node + linkType: hard + "@aws-sdk/credential-provider-node@npm:3.105.0": version: 3.105.0 resolution: "@aws-sdk/credential-provider-node@npm:3.105.0" @@ -866,6 +1280,26 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/credential-provider-node@npm:3.577.0": + version: 3.577.0 + resolution: "@aws-sdk/credential-provider-node@npm:3.577.0" + dependencies: + "@aws-sdk/credential-provider-env": "npm:3.577.0" + "@aws-sdk/credential-provider-http": "npm:3.577.0" + "@aws-sdk/credential-provider-ini": "npm:3.577.0" + "@aws-sdk/credential-provider-process": "npm:3.577.0" + "@aws-sdk/credential-provider-sso": "npm:3.577.0" + "@aws-sdk/credential-provider-web-identity": "npm:3.577.0" + "@aws-sdk/types": "npm:3.577.0" + "@smithy/credential-provider-imds": "npm:^3.0.0" + "@smithy/property-provider": "npm:^3.0.0" + "@smithy/shared-ini-file-loader": "npm:^3.0.0" + "@smithy/types": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: 61e7b82a98f97ab62a1c167727f015e3a12e51c351c2dc26a7e942a72a2ef5d2f36759aa8e3b3b2799f2de1bcbeaeccee4f8fe1f36626dfd3a0648147af35d78 + languageName: node + linkType: hard + "@aws-sdk/credential-provider-process@npm:3.127.0": version: 3.127.0 resolution: "@aws-sdk/credential-provider-process@npm:3.127.0" @@ -891,6 +1325,19 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/credential-provider-process@npm:3.577.0": + version: 3.577.0 + resolution: "@aws-sdk/credential-provider-process@npm:3.577.0" + dependencies: + "@aws-sdk/types": "npm:3.577.0" + "@smithy/property-provider": "npm:^3.0.0" + "@smithy/shared-ini-file-loader": "npm:^3.0.0" + "@smithy/types": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: deb5b00cd052440705ca86df618d77862dc024c50efcc74a80975523076e634ad9570cdd78ada35dad486f721e8aaed95f0a3e9bf99f249e9d722eb8f4b434f7 + languageName: node + linkType: hard + "@aws-sdk/credential-provider-process@npm:3.80.0": version: 3.80.0 resolution: "@aws-sdk/credential-provider-process@npm:3.80.0" @@ -943,6 +1390,21 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/credential-provider-sso@npm:3.577.0": + version: 3.577.0 + resolution: "@aws-sdk/credential-provider-sso@npm:3.577.0" + dependencies: + "@aws-sdk/client-sso": "npm:3.577.0" + "@aws-sdk/token-providers": "npm:3.577.0" + "@aws-sdk/types": "npm:3.577.0" + "@smithy/property-provider": "npm:^3.0.0" + "@smithy/shared-ini-file-loader": "npm:^3.0.0" + "@smithy/types": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: 43ca5e4c1843301d77d6987b94484730fcf8bd0ce7913a49a0763138da9e911e5b259a195e3a2a674520c80aef9f1bfbb7a6b843bd61e6310a52828fe455b63f + languageName: node + linkType: hard + "@aws-sdk/credential-provider-web-identity@npm:3.127.0": version: 3.127.0 resolution: "@aws-sdk/credential-provider-web-identity@npm:3.127.0" @@ -965,6 +1427,20 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/credential-provider-web-identity@npm:3.577.0": + version: 3.577.0 + resolution: "@aws-sdk/credential-provider-web-identity@npm:3.577.0" + dependencies: + "@aws-sdk/types": "npm:3.577.0" + "@smithy/property-provider": "npm:^3.0.0" + "@smithy/types": "npm:^3.0.0" + tslib: "npm:^2.6.2" + peerDependencies: + "@aws-sdk/client-sts": ^3.577.0 + checksum: f7f297c2fdae913c6671b83ae9e89864c27941699b443993dc4c1eb20648adac335fa07ebf0aefffb9bec3e0cb78a5e6a93a778b65d0d192dad4d3a0942f38fe + languageName: node + linkType: hard + "@aws-sdk/credential-provider-web-identity@npm:3.78.0": version: 3.78.0 resolution: "@aws-sdk/credential-provider-web-identity@npm:3.78.0" @@ -1187,6 +1663,21 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/middleware-bucket-endpoint@npm:3.577.0": + version: 3.577.0 + resolution: "@aws-sdk/middleware-bucket-endpoint@npm:3.577.0" + dependencies: + "@aws-sdk/types": "npm:3.577.0" + "@aws-sdk/util-arn-parser": "npm:3.568.0" + "@smithy/node-config-provider": "npm:^3.0.0" + "@smithy/protocol-http": "npm:^4.0.0" + "@smithy/types": "npm:^3.0.0" + "@smithy/util-config-provider": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: 87e3822883c2295a170a98db36e9fe275559a5dd1b5e2baf4c1b69bb6d3147a0c12e2e4991af4db68ee823d3b7dc44089448a5b846de0384e9a19424f78e4bd9 + languageName: node + linkType: hard + "@aws-sdk/middleware-bucket-endpoint@npm:3.80.0": version: 3.80.0 resolution: "@aws-sdk/middleware-bucket-endpoint@npm:3.80.0" @@ -1233,6 +1724,18 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/middleware-expect-continue@npm:3.577.0": + version: 3.577.0 + resolution: "@aws-sdk/middleware-expect-continue@npm:3.577.0" + dependencies: + "@aws-sdk/types": "npm:3.577.0" + "@smithy/protocol-http": "npm:^4.0.0" + "@smithy/types": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: 81f076341de3e5dd5c6dc1c23be223e42dbad5118ac423e95b3cd783ee39a9085c94fe710f17329520f52807373795636a43ac9ea9fb373455e5ce5df9a63291 + languageName: node + linkType: hard + "@aws-sdk/middleware-expect-continue@npm:3.78.0": version: 3.78.0 resolution: "@aws-sdk/middleware-expect-continue@npm:3.78.0" @@ -1245,6 +1748,22 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/middleware-flexible-checksums@npm:3.577.0": + version: 3.577.0 + resolution: "@aws-sdk/middleware-flexible-checksums@npm:3.577.0" + dependencies: + "@aws-crypto/crc32": "npm:3.0.0" + "@aws-crypto/crc32c": "npm:3.0.0" + "@aws-sdk/types": "npm:3.577.0" + "@smithy/is-array-buffer": "npm:^3.0.0" + "@smithy/protocol-http": "npm:^4.0.0" + "@smithy/types": "npm:^3.0.0" + "@smithy/util-utf8": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: f219bcfda7bacd8fa5b0038f69a1ba4bac04460ca33105849b5f88dfe9df4d46464d66d44d43a7ed246be2eea5e8ecad62be78476bc6efdd9e5db22bc1547231 + languageName: node + linkType: hard + "@aws-sdk/middleware-flexible-checksums@npm:3.78.0": version: 3.78.0 resolution: "@aws-sdk/middleware-flexible-checksums@npm:3.78.0" @@ -1292,6 +1811,18 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/middleware-host-header@npm:3.577.0": + version: 3.577.0 + resolution: "@aws-sdk/middleware-host-header@npm:3.577.0" + dependencies: + "@aws-sdk/types": "npm:3.577.0" + "@smithy/protocol-http": "npm:^4.0.0" + "@smithy/types": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: 83e28305f05d88450f93ffc2ea49288b407413054cb29dd969313a4b0df4910090fd6495314820fa2e59f951e21399ed73e7425a66ef98d777c1702ba5c103f4 + languageName: node + linkType: hard + "@aws-sdk/middleware-host-header@npm:3.78.0": version: 3.78.0 resolution: "@aws-sdk/middleware-host-header@npm:3.78.0" @@ -1303,6 +1834,17 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/middleware-location-constraint@npm:3.577.0": + version: 3.577.0 + resolution: "@aws-sdk/middleware-location-constraint@npm:3.577.0" + dependencies: + "@aws-sdk/types": "npm:3.577.0" + "@smithy/types": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: deeecf19c78f8aefe17c205104482288f851baa4c3eaa9424f59d3d5b371b8fb6e764f21377721d50ad8e0121cb005a7dbace44d7bab22d1fe95a46a2d4340d3 + languageName: node + linkType: hard + "@aws-sdk/middleware-location-constraint@npm:3.78.0": version: 3.78.0 resolution: "@aws-sdk/middleware-location-constraint@npm:3.78.0" @@ -1333,6 +1875,17 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/middleware-logger@npm:3.577.0": + version: 3.577.0 + resolution: "@aws-sdk/middleware-logger@npm:3.577.0" + dependencies: + "@aws-sdk/types": "npm:3.577.0" + "@smithy/types": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: b1dd50f776aac1391fc6a114d06bea175cb7e6b7e2300125e624fccf32a28176910fc32746f70cef1ddfec8d9d4d66729dad0ecb4fd3b14d48e0a1dda7192754 + languageName: node + linkType: hard + "@aws-sdk/middleware-logger@npm:3.78.0": version: 3.78.0 resolution: "@aws-sdk/middleware-logger@npm:3.78.0" @@ -1365,6 +1918,18 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/middleware-recursion-detection@npm:3.577.0": + version: 3.577.0 + resolution: "@aws-sdk/middleware-recursion-detection@npm:3.577.0" + dependencies: + "@aws-sdk/types": "npm:3.577.0" + "@smithy/protocol-http": "npm:^4.0.0" + "@smithy/types": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: 375b9dd945c10f941e7f86697c38f1401cd225dd28ae264a21d0f2c88c5c89585af18b759687f139b4441c6402278c634a4f8baacb53070299cee18ecea47dba + languageName: node + linkType: hard + "@aws-sdk/middleware-retry@npm:3.127.0": version: 3.127.0 resolution: "@aws-sdk/middleware-retry@npm:3.127.0" @@ -1418,6 +1983,23 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/middleware-sdk-s3@npm:3.577.0": + version: 3.577.0 + resolution: "@aws-sdk/middleware-sdk-s3@npm:3.577.0" + dependencies: + "@aws-sdk/types": "npm:3.577.0" + "@aws-sdk/util-arn-parser": "npm:3.568.0" + "@smithy/node-config-provider": "npm:^3.0.0" + "@smithy/protocol-http": "npm:^4.0.0" + "@smithy/signature-v4": "npm:^3.0.0" + "@smithy/smithy-client": "npm:^3.0.0" + "@smithy/types": "npm:^3.0.0" + "@smithy/util-config-provider": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: 60b654d8e6167f2b4081f2ccbe7c36d77eae56b4005438e9a41f33f17120f11a00ed647a2827b5188aff25053f30f55ae3d5db955ee5a066a5c54fc100105264 + languageName: node + linkType: hard + "@aws-sdk/middleware-sdk-sts@npm:3.130.0": version: 3.130.0 resolution: "@aws-sdk/middleware-sdk-sts@npm:3.130.0" @@ -1516,6 +2098,21 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/middleware-signing@npm:3.577.0": + version: 3.577.0 + resolution: "@aws-sdk/middleware-signing@npm:3.577.0" + dependencies: + "@aws-sdk/types": "npm:3.577.0" + "@smithy/property-provider": "npm:^3.0.0" + "@smithy/protocol-http": "npm:^4.0.0" + "@smithy/signature-v4": "npm:^3.0.0" + "@smithy/types": "npm:^3.0.0" + "@smithy/util-middleware": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: 51c0e9b82d01ed0c8d2b5b9b5299eec15e6bc64b7b448be730ef861c3d9334ce49fccc889d913864f836da9db6f28f3538c8385493be3a432a5d8517b0ace551 + languageName: node + linkType: hard + "@aws-sdk/middleware-signing@npm:3.78.0": version: 3.78.0 resolution: "@aws-sdk/middleware-signing@npm:3.78.0" @@ -1529,6 +2126,17 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/middleware-ssec@npm:3.577.0": + version: 3.577.0 + resolution: "@aws-sdk/middleware-ssec@npm:3.577.0" + dependencies: + "@aws-sdk/types": "npm:3.577.0" + "@smithy/types": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: 92f6a68e12b9ecdb644cf30a5bf076517496203223642cdcd0f12faa5882118100a1ae67de2b44ec26c7b393cdcd1d5c79af42ec6ac523f94c478cd7a546e335 + languageName: node + linkType: hard + "@aws-sdk/middleware-ssec@npm:3.78.0": version: 3.78.0 resolution: "@aws-sdk/middleware-ssec@npm:3.78.0" @@ -1588,6 +2196,19 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/middleware-user-agent@npm:3.577.0": + version: 3.577.0 + resolution: "@aws-sdk/middleware-user-agent@npm:3.577.0" + dependencies: + "@aws-sdk/types": "npm:3.577.0" + "@aws-sdk/util-endpoints": "npm:3.577.0" + "@smithy/protocol-http": "npm:^4.0.0" + "@smithy/types": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: 1419935c015d0a23dceb597f9fff2ef04c6dbb9f211c3c954bed1bacd85d9a662f7f86e948ebb6d1099231084528fb753f70057a90daec8a3e4f2b0f32f31075 + languageName: node + linkType: hard + "@aws-sdk/middleware-user-agent@npm:3.78.0": version: 3.78.0 resolution: "@aws-sdk/middleware-user-agent@npm:3.78.0" @@ -1797,6 +2418,20 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/region-config-resolver@npm:3.577.0": + version: 3.577.0 + resolution: "@aws-sdk/region-config-resolver@npm:3.577.0" + dependencies: + "@aws-sdk/types": "npm:3.577.0" + "@smithy/node-config-provider": "npm:^3.0.0" + "@smithy/types": "npm:^3.0.0" + "@smithy/util-config-provider": "npm:^3.0.0" + "@smithy/util-middleware": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: c17be35a8a9f9a92a77190e38db5f218d12ada04bff531d72c888ee81f87c8faaab87756f80b312fab9aec51ba7662b0222c4562b0950fe8a751e04365516606 + languageName: node + linkType: hard + "@aws-sdk/service-error-classification@npm:3.127.0": version: 3.127.0 resolution: "@aws-sdk/service-error-classification@npm:3.127.0" @@ -1845,6 +2480,20 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/signature-v4-multi-region@npm:3.577.0": + version: 3.577.0 + resolution: "@aws-sdk/signature-v4-multi-region@npm:3.577.0" + dependencies: + "@aws-sdk/middleware-sdk-s3": "npm:3.577.0" + "@aws-sdk/types": "npm:3.577.0" + "@smithy/protocol-http": "npm:^4.0.0" + "@smithy/signature-v4": "npm:^3.0.0" + "@smithy/types": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: 9f8784abdc1a95d2ce91accf0be038d4b08f959ea0ae1ed3ef5fb868fdd3d65b1aa1fca37a94ff2d1517c50a1213d64b1daedc0b891c1daf9bdef738ce68f994 + languageName: node + linkType: hard + "@aws-sdk/signature-v4-multi-region@npm:3.88.0": version: 3.88.0 resolution: "@aws-sdk/signature-v4-multi-region@npm:3.88.0" @@ -1937,6 +2586,21 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/token-providers@npm:3.577.0": + version: 3.577.0 + resolution: "@aws-sdk/token-providers@npm:3.577.0" + dependencies: + "@aws-sdk/types": "npm:3.577.0" + "@smithy/property-provider": "npm:^3.0.0" + "@smithy/shared-ini-file-loader": "npm:^3.0.0" + "@smithy/types": "npm:^3.0.0" + tslib: "npm:^2.6.2" + peerDependencies: + "@aws-sdk/client-sso-oidc": ^3.577.0 + checksum: 94e0e6e1cea556bbba4abf574bdc24590ebcf86a75d675e9f54283c764bec9b0da3b1c9bd76c6eee2e3fd6eeefa59012f64832fc2d96761f064119320ab78529 + languageName: node + linkType: hard + "@aws-sdk/types@npm:3.127.0": version: 3.127.0 resolution: "@aws-sdk/types@npm:3.127.0" @@ -1951,6 +2615,16 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/types@npm:3.577.0, @aws-sdk/types@npm:^3.222.0": + version: 3.577.0 + resolution: "@aws-sdk/types@npm:3.577.0" + dependencies: + "@smithy/types": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: a384ea32fcd0ae02a8058aa1f0d370de4c54f38a5e88bf649090e85bde09ef36efca49351f88669152626826441a7de8a3b1dfd0d6886553eeb2234d5205a196 + languageName: node + linkType: hard + "@aws-sdk/types@npm:3.78.0, @aws-sdk/types@npm:^3.1.0": version: 3.78.0 resolution: "@aws-sdk/types@npm:3.78.0" @@ -2000,6 +2674,15 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/util-arn-parser@npm:3.568.0": + version: 3.568.0 + resolution: "@aws-sdk/util-arn-parser@npm:3.568.0" + dependencies: + tslib: "npm:^2.6.2" + checksum: b1a7f93b4f47136ee8d71bcbbd2d5d19581007f0684aff252d3bee6b9ccc7c56e765255bb1bea847171b40cdbd2eca0fb102f24cba857d1c79c54747e8ee0855 + languageName: node + linkType: hard + "@aws-sdk/util-base64-browser@npm:3.109.0": version: 3.109.0 resolution: "@aws-sdk/util-base64-browser@npm:3.109.0" @@ -2218,6 +2901,18 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/util-endpoints@npm:3.577.0": + version: 3.577.0 + resolution: "@aws-sdk/util-endpoints@npm:3.577.0" + dependencies: + "@aws-sdk/types": "npm:3.577.0" + "@smithy/types": "npm:^3.0.0" + "@smithy/util-endpoints": "npm:^2.0.0" + tslib: "npm:^2.6.2" + checksum: e5aac9f02b4ec24c6392b4e9fa8646771d39b8c4feedc7ac129cf5467585391bb17f11c27b04105113304d3fc97c3424ad0ff29dc4253de0b3ef459749dc87d6 + languageName: node + linkType: hard + "@aws-sdk/util-hex-encoding@npm:3.109.0": version: 3.109.0 resolution: "@aws-sdk/util-hex-encoding@npm:3.109.0" @@ -2332,6 +3027,18 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/util-user-agent-browser@npm:3.577.0": + version: 3.577.0 + resolution: "@aws-sdk/util-user-agent-browser@npm:3.577.0" + dependencies: + "@aws-sdk/types": "npm:3.577.0" + "@smithy/types": "npm:^3.0.0" + bowser: "npm:^2.11.0" + tslib: "npm:^2.6.2" + checksum: 11a0f949a0f3ff09e557d4d1c0ccc384db1753d5029d0aaa325f12482294199225e6027c9e62aab7273a84429630ad3c94d69fd7c87678ac22da896e75f4e992 + languageName: node + linkType: hard + "@aws-sdk/util-user-agent-browser@npm:3.78.0": version: 3.78.0 resolution: "@aws-sdk/util-user-agent-browser@npm:3.78.0" @@ -2370,6 +3077,23 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/util-user-agent-node@npm:3.577.0": + version: 3.577.0 + resolution: "@aws-sdk/util-user-agent-node@npm:3.577.0" + dependencies: + "@aws-sdk/types": "npm:3.577.0" + "@smithy/node-config-provider": "npm:^3.0.0" + "@smithy/types": "npm:^3.0.0" + tslib: "npm:^2.6.2" + peerDependencies: + aws-crt: ">=1.0.0" + peerDependenciesMeta: + aws-crt: + optional: true + checksum: 494bae9a69d903fae1725ab833e39af207da50642f86ec3ae554694a548f66b0c8ca24fcc1967b8d197b71bf87f5f82b95b4782ca1d331fdefdeb3207b40a9b1 + languageName: node + linkType: hard + "@aws-sdk/util-user-agent-node@npm:3.80.0": version: 3.80.0 resolution: "@aws-sdk/util-user-agent-node@npm:3.80.0" @@ -2458,6 +3182,16 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/xml-builder@npm:3.575.0": + version: 3.575.0 + resolution: "@aws-sdk/xml-builder@npm:3.575.0" + dependencies: + "@smithy/types": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: b1ca3cca6d49a10453d9b82242a4fbf2e5867d22c6238ea44f4210bdb64a2a48996b36d3662718995a76308bf4af7a834df7ba8498fba4d5aaabd2f7362f36f1 + languageName: node + linkType: hard + "@babel/code-frame@npm:^7.0.0": version: 7.16.7 resolution: "@babel/code-frame@npm:7.16.7" @@ -4954,15 +5688,18 @@ __metadata: version: 0.0.0-use.local resolution: "@hyperlane-xyz/cli@workspace:typescript/cli" dependencies: - "@hyperlane-xyz/registry": "npm:^1.0.7" - "@hyperlane-xyz/sdk": "npm:3.12.2" - "@hyperlane-xyz/utils": "npm:3.12.2" + "@aws-sdk/client-kms": "npm:^3.577.0" + "@aws-sdk/client-s3": "npm:^3.577.0" + "@hyperlane-xyz/registry": "npm:1.3.0" + "@hyperlane-xyz/sdk": "npm:3.13.0" + "@hyperlane-xyz/utils": "npm:3.13.0" "@inquirer/prompts": "npm:^3.0.0" "@types/mocha": "npm:^10.0.1" "@types/node": "npm:^18.14.5" "@types/yargs": "npm:^17.0.24" "@typescript-eslint/eslint-plugin": "npm:^7.4.0" "@typescript-eslint/parser": "npm:^7.4.0" + asn1.js: "npm:^5.4.1" bignumber.js: "npm:^9.1.1" chai: "npm:^4.3.6" chalk: "npm:^5.3.0" @@ -4983,12 +5720,12 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/core@npm:3.12.2, @hyperlane-xyz/core@workspace:solidity": +"@hyperlane-xyz/core@npm:3.13.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.12.2" + "@hyperlane-xyz/utils": "npm:3.13.0" "@layerzerolabs/lz-evm-oapp-v2": "npm:2.0.2" "@layerzerolabs/solidity-examples": "npm:^1.1.0" "@nomiclabs/hardhat-ethers": "npm:^2.2.3" @@ -5036,13 +5773,13 @@ __metadata: languageName: node linkType: hard -"@hyperlane-xyz/helloworld@npm:3.12.2, @hyperlane-xyz/helloworld@workspace:typescript/helloworld": +"@hyperlane-xyz/helloworld@npm:3.13.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.12.2" - "@hyperlane-xyz/registry": "npm:^1.0.7" - "@hyperlane-xyz/sdk": "npm:3.12.2" + "@hyperlane-xyz/core": "npm:3.13.0" + "@hyperlane-xyz/registry": "npm:1.3.0" + "@hyperlane-xyz/sdk": "npm:3.13.0" "@nomiclabs/hardhat-ethers": "npm:^2.2.3" "@nomiclabs/hardhat-waffle": "npm:^2.0.6" "@openzeppelin/contracts-upgradeable": "npm:^4.9.3" @@ -5087,10 +5824,10 @@ __metadata: "@ethersproject/experimental": "npm:^5.7.0" "@ethersproject/hardware-wallets": "npm:^5.7.0" "@ethersproject/providers": "npm:^5.7.2" - "@hyperlane-xyz/helloworld": "npm:3.12.2" - "@hyperlane-xyz/registry": "npm:^1.0.7" - "@hyperlane-xyz/sdk": "npm:3.12.2" - "@hyperlane-xyz/utils": "npm:3.12.2" + "@hyperlane-xyz/helloworld": "npm:3.13.0" + "@hyperlane-xyz/registry": "npm:1.3.0" + "@hyperlane-xyz/sdk": "npm:3.13.0" + "@hyperlane-xyz/utils": "npm:3.13.0" "@nomiclabs/hardhat-ethers": "npm:^2.2.3" "@nomiclabs/hardhat-etherscan": "npm:^3.0.3" "@nomiclabs/hardhat-waffle": "npm:^2.0.6" @@ -5140,24 +5877,25 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/registry@npm:^1.0.7": - version: 1.0.7 - resolution: "@hyperlane-xyz/registry@npm:1.0.7" +"@hyperlane-xyz/registry@npm:1.3.0": + version: 1.3.0 + resolution: "@hyperlane-xyz/registry@npm:1.3.0" dependencies: yaml: "npm:^2" zod: "npm:^3.21.2" - checksum: fb112e2a1fdec539c6ef457ab44b9b5a835b719f2d4cc5cb0efb4413e2647402ed826d597f4c63b552f86fcf91bf69d4d1e5934aee8498f83dac61007a9d5650 + checksum: 2cbdfd9e8958d0babde7104dfb0c98def7edb5f87f5f4679b09467a6a9b531884f187fcbc16fd85b00e304ef8fa3beb0a0779555b2c3edc1936541a0e878a73d languageName: node linkType: hard -"@hyperlane-xyz/sdk@npm:3.12.2, @hyperlane-xyz/sdk@workspace:typescript/sdk": +"@hyperlane-xyz/sdk@npm:3.13.0, @hyperlane-xyz/sdk@workspace:typescript/sdk": version: 0.0.0-use.local resolution: "@hyperlane-xyz/sdk@workspace:typescript/sdk" dependencies: + "@aws-sdk/client-s3": "npm:^3.74.0" "@cosmjs/cosmwasm-stargate": "npm:^0.31.3" "@cosmjs/stargate": "npm:^0.31.3" - "@hyperlane-xyz/core": "npm:3.12.2" - "@hyperlane-xyz/utils": "npm:3.12.2" + "@hyperlane-xyz/core": "npm:3.13.0" + "@hyperlane-xyz/utils": "npm:3.13.0" "@nomiclabs/hardhat-ethers": "npm:^2.2.3" "@nomiclabs/hardhat-waffle": "npm:^2.0.6" "@safe-global/api-kit": "npm:1.3.0" @@ -5225,7 +5963,7 @@ __metadata: languageName: node linkType: hard -"@hyperlane-xyz/utils@npm:3.12.2, @hyperlane-xyz/utils@workspace:typescript/utils": +"@hyperlane-xyz/utils@npm:3.13.0, @hyperlane-xyz/utils@workspace:typescript/utils": version: 0.0.0-use.local resolution: "@hyperlane-xyz/utils@workspace:typescript/utils" dependencies: @@ -7578,6 +8316,570 @@ __metadata: languageName: node linkType: hard +"@smithy/abort-controller@npm:^3.0.0": + version: 3.0.0 + resolution: "@smithy/abort-controller@npm:3.0.0" + dependencies: + "@smithy/types": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: 08bf21e79226c60f3654767683a767b34dd7b30d7fd73dfecd4cb13d172a7225f83f45553fd4af2692c95d38bdf2adbe5b132fac0affb4ecece6a41f066d49ba + languageName: node + linkType: hard + +"@smithy/chunked-blob-reader-native@npm:^3.0.0": + version: 3.0.0 + resolution: "@smithy/chunked-blob-reader-native@npm:3.0.0" + dependencies: + "@smithy/util-base64": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: 424aa83f4fc081625a03ec6c64e74ae38c740c0b202d0b998f2bf341b935613491b39c7bf701790a0625219424340d5cfb042b701bfdff4c1cbedc57ee3f2500 + languageName: node + linkType: hard + +"@smithy/chunked-blob-reader@npm:^3.0.0": + version: 3.0.0 + resolution: "@smithy/chunked-blob-reader@npm:3.0.0" + dependencies: + tslib: "npm:^2.6.2" + checksum: 1c7955ae693aa098dd0839d7e8f9e742ab963de4ededa92f201f1982552c35ba625c1b90cf761de81deddd5002ed10f081ad46f6e0a5150066cee8b00f3f6058 + languageName: node + linkType: hard + +"@smithy/config-resolver@npm:^3.0.0": + version: 3.0.0 + resolution: "@smithy/config-resolver@npm:3.0.0" + dependencies: + "@smithy/node-config-provider": "npm:^3.0.0" + "@smithy/types": "npm:^3.0.0" + "@smithy/util-config-provider": "npm:^3.0.0" + "@smithy/util-middleware": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: 78d6a13cb7d8c64ffe4aff675f5fb7114355b406be307576b7f77f880769417ba8e02830dcbb0991dd933c00e9e1e6248706a60e97c98bcf302577bd79ec52e0 + languageName: node + linkType: hard + +"@smithy/core@npm:^2.0.0": + version: 2.0.1 + resolution: "@smithy/core@npm:2.0.1" + dependencies: + "@smithy/middleware-endpoint": "npm:^3.0.0" + "@smithy/middleware-retry": "npm:^3.0.1" + "@smithy/middleware-serde": "npm:^3.0.0" + "@smithy/protocol-http": "npm:^4.0.0" + "@smithy/smithy-client": "npm:^3.0.1" + "@smithy/types": "npm:^3.0.0" + "@smithy/util-middleware": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: 3c951d33e0b7c402d7e6b1f6f16bf0a29625b0edba6c68dc7f991e4e90a9c80bb9951e9fe1e1865939d37fedfd7bb0674352a8f973493ab4aa0d313c1c1427ac + languageName: node + linkType: hard + +"@smithy/credential-provider-imds@npm:^3.0.0": + version: 3.0.0 + resolution: "@smithy/credential-provider-imds@npm:3.0.0" + dependencies: + "@smithy/node-config-provider": "npm:^3.0.0" + "@smithy/property-provider": "npm:^3.0.0" + "@smithy/types": "npm:^3.0.0" + "@smithy/url-parser": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: 793e826a6d2ea1d407a4a0329662b41bc30d2e052520af27845bbd4345f454e1974e389fce622c26b06501c7d5a3c4b3844ec99baedb27e8f89d947d2c28fee6 + languageName: node + linkType: hard + +"@smithy/eventstream-codec@npm:^3.0.0": + version: 3.0.0 + resolution: "@smithy/eventstream-codec@npm:3.0.0" + dependencies: + "@aws-crypto/crc32": "npm:3.0.0" + "@smithy/types": "npm:^3.0.0" + "@smithy/util-hex-encoding": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: 66ec273253d59c78ff7967d1fcd56c2479dc5807af360773dcad3d669b75a75682f3cd3c6647bda0ecc3c42cce6ea7d6261fd109e9531b209a51b9b6e93d7c2c + languageName: node + linkType: hard + +"@smithy/eventstream-serde-browser@npm:^3.0.0": + version: 3.0.0 + resolution: "@smithy/eventstream-serde-browser@npm:3.0.0" + dependencies: + "@smithy/eventstream-serde-universal": "npm:^3.0.0" + "@smithy/types": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: a04c6a5207e670ce0c5508d92a70f5372a5fca076e3a7d76e5383753622525042225fdfe8c3cc0b3ee723ce0e7b568b7d2603ed83337538d471a8817c8d4306d + languageName: node + linkType: hard + +"@smithy/eventstream-serde-config-resolver@npm:^3.0.0": + version: 3.0.0 + resolution: "@smithy/eventstream-serde-config-resolver@npm:3.0.0" + dependencies: + "@smithy/types": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: c07f698072bc1ddec3fc54cecbe03fece2b86445e9c18183f2621c528258d120a63d3b02b7d6d92fe3e0a73d29275ce18d85a253ebbdb06973666f885586ce72 + languageName: node + linkType: hard + +"@smithy/eventstream-serde-node@npm:^3.0.0": + version: 3.0.0 + resolution: "@smithy/eventstream-serde-node@npm:3.0.0" + dependencies: + "@smithy/eventstream-serde-universal": "npm:^3.0.0" + "@smithy/types": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: 46921fae3e47926ef31879dffc3e9743bf2b696a7d4b083c3ddfd06568a30d4fe021a70847d8c8561b9637ba01c4cdfdbd7ba3c418977096945a6cb604b041ea + languageName: node + linkType: hard + +"@smithy/eventstream-serde-universal@npm:^3.0.0": + version: 3.0.0 + resolution: "@smithy/eventstream-serde-universal@npm:3.0.0" + dependencies: + "@smithy/eventstream-codec": "npm:^3.0.0" + "@smithy/types": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: 6bca5785416e8674a032cffab6fd7f93e8a71ba048d300f46e9da82973750e8709c5981c6aa7de2ebe82e01d22eb1df2383b5c5093beedda5f3e600482e84559 + languageName: node + linkType: hard + +"@smithy/fetch-http-handler@npm:^3.0.0, @smithy/fetch-http-handler@npm:^3.0.1": + version: 3.0.1 + resolution: "@smithy/fetch-http-handler@npm:3.0.1" + dependencies: + "@smithy/protocol-http": "npm:^4.0.0" + "@smithy/querystring-builder": "npm:^3.0.0" + "@smithy/types": "npm:^3.0.0" + "@smithy/util-base64": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: 46da52bf2320cd279f11585fd2ce414557f3fdf283b969a4b9ed1b0093d23b099bc93edf992f97e99d5c672e5ebfae5072595f8da1fc2738b8f2ea3ead200a90 + languageName: node + linkType: hard + +"@smithy/hash-blob-browser@npm:^3.0.0": + version: 3.0.0 + resolution: "@smithy/hash-blob-browser@npm:3.0.0" + dependencies: + "@smithy/chunked-blob-reader": "npm:^3.0.0" + "@smithy/chunked-blob-reader-native": "npm:^3.0.0" + "@smithy/types": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: c8f6d76d8dae37bd48334bcd6060195d8d8f6b14d0af7d326cbd7f3dd4db648691bf755f3795e5d6b4ca9b85fbc580a4b010cce6fe9735204e8a667f145f11bb + languageName: node + linkType: hard + +"@smithy/hash-node@npm:^3.0.0": + version: 3.0.0 + resolution: "@smithy/hash-node@npm:3.0.0" + dependencies: + "@smithy/types": "npm:^3.0.0" + "@smithy/util-buffer-from": "npm:^3.0.0" + "@smithy/util-utf8": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: 99c65bc992bc3adedb68d4304845bf0acd3e55d3cd851874605a937be5dd4da4eeab01e99e971b59d43d6fb4364dab655530c3a89eb32eac0803f6d07179a63b + languageName: node + linkType: hard + +"@smithy/hash-stream-node@npm:^3.0.0": + version: 3.0.0 + resolution: "@smithy/hash-stream-node@npm:3.0.0" + dependencies: + "@smithy/types": "npm:^3.0.0" + "@smithy/util-utf8": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: 1e67ad794267dcbf2f5008938409b4081e43cbf302d44508f444aaa051ca167e1739418333122b9cce4b98b0815f618326e9c2d55fd5579751ad22ac7e02c9d2 + languageName: node + linkType: hard + +"@smithy/invalid-dependency@npm:^3.0.0": + version: 3.0.0 + resolution: "@smithy/invalid-dependency@npm:3.0.0" + dependencies: + "@smithy/types": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: e78e9cbe1cc8ad04be0dffbc1094eb15294d29b86389ae62c55f0afb96b7354c615fc20f34affed362f857d497e7b34e04b51e732f0b045c2870b749ecc5a2f4 + languageName: node + linkType: hard + +"@smithy/is-array-buffer@npm:^3.0.0": + version: 3.0.0 + resolution: "@smithy/is-array-buffer@npm:3.0.0" + dependencies: + tslib: "npm:^2.6.2" + checksum: cab1fd4033d9863dcd95ff058463eb591574bd47e6b61e36aaaf4c0d0da9ed966a54e1d33ec4db7d67aa85df7d274203e934e04dbb40323d01ef4815f63997fc + languageName: node + linkType: hard + +"@smithy/md5-js@npm:^3.0.0": + version: 3.0.0 + resolution: "@smithy/md5-js@npm:3.0.0" + dependencies: + "@smithy/types": "npm:^3.0.0" + "@smithy/util-utf8": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: 098b849ee76c83fb33624dac8d3980a50873564de6fae4e159bac90a6aa9abe0b9fe0fce9a150e5ff438e0a8af2010c50cdc08dd2a8d02b7db2ebb89802743b9 + languageName: node + linkType: hard + +"@smithy/middleware-content-length@npm:^3.0.0": + version: 3.0.0 + resolution: "@smithy/middleware-content-length@npm:3.0.0" + dependencies: + "@smithy/protocol-http": "npm:^4.0.0" + "@smithy/types": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: 2d1dc5766ac83604d43b75b788d4b1f61d8095c9081fe060d5bb21d69b59c4da52869d38eb4f9e13ca8001974b3bce63619f1d8bddfc553041e5b264162fdac9 + languageName: node + linkType: hard + +"@smithy/middleware-endpoint@npm:^3.0.0": + version: 3.0.0 + resolution: "@smithy/middleware-endpoint@npm:3.0.0" + dependencies: + "@smithy/middleware-serde": "npm:^3.0.0" + "@smithy/node-config-provider": "npm:^3.0.0" + "@smithy/shared-ini-file-loader": "npm:^3.0.0" + "@smithy/types": "npm:^3.0.0" + "@smithy/url-parser": "npm:^3.0.0" + "@smithy/util-middleware": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: b39b8a3c8ddc4295ab265ce861360a7a842c94af7fb75d81aba4a89000715e50598138f1a7da4979675738d391472189e9854d35cae10a9e994245ad69c2682f + languageName: node + linkType: hard + +"@smithy/middleware-retry@npm:^3.0.0, @smithy/middleware-retry@npm:^3.0.1": + version: 3.0.1 + resolution: "@smithy/middleware-retry@npm:3.0.1" + dependencies: + "@smithy/node-config-provider": "npm:^3.0.0" + "@smithy/protocol-http": "npm:^4.0.0" + "@smithy/service-error-classification": "npm:^3.0.0" + "@smithy/smithy-client": "npm:^3.0.1" + "@smithy/types": "npm:^3.0.0" + "@smithy/util-middleware": "npm:^3.0.0" + "@smithy/util-retry": "npm:^3.0.0" + tslib: "npm:^2.6.2" + uuid: "npm:^9.0.1" + checksum: 637cadce7fd0b2a22358d43fd3601129e8b725b5f742d835be187882a46690173986d3d7172a00ddbea31c69e4ecef2ed911c457acdb1d3c96adfb56475cbe23 + languageName: node + linkType: hard + +"@smithy/middleware-serde@npm:^3.0.0": + version: 3.0.0 + resolution: "@smithy/middleware-serde@npm:3.0.0" + dependencies: + "@smithy/types": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: 7ca5256fe9290b6ae097fdb9c0180e5219e6d3cb39084fadee007d9e698073498d200c32c439486902e386ab76739176765f64d23673882a08aa0e8de837dc8a + languageName: node + linkType: hard + +"@smithy/middleware-stack@npm:^3.0.0": + version: 3.0.0 + resolution: "@smithy/middleware-stack@npm:3.0.0" + dependencies: + "@smithy/types": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: e85695b2d2d96230f03500b7111f9917abaab516e1850ec90021db7e984718965e05f7afccda084a7ba96a6bbb9d195a7d6e7882b48d7ccec97239101a2978bc + languageName: node + linkType: hard + +"@smithy/node-config-provider@npm:^3.0.0": + version: 3.0.0 + resolution: "@smithy/node-config-provider@npm:3.0.0" + dependencies: + "@smithy/property-provider": "npm:^3.0.0" + "@smithy/shared-ini-file-loader": "npm:^3.0.0" + "@smithy/types": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: 6f5326162484f27c6709796e6f11aaa1cd624cb0632a09340f2f2126c20c64dd10f9ed96400f1e65afdfa11e877f69910951ea2b36264141cc513c51461ac656 + languageName: node + linkType: hard + +"@smithy/node-http-handler@npm:^3.0.0": + version: 3.0.0 + resolution: "@smithy/node-http-handler@npm:3.0.0" + dependencies: + "@smithy/abort-controller": "npm:^3.0.0" + "@smithy/protocol-http": "npm:^4.0.0" + "@smithy/querystring-builder": "npm:^3.0.0" + "@smithy/types": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: 3d2d0fff55ebeabeca4bb9a8b1cc7dd3a9c810281817232eb546b75cfae53da3c9571d25f203232f42cef608f28b795a1cadec3dbfd44aad2029a6141d146ecf + languageName: node + linkType: hard + +"@smithy/property-provider@npm:^3.0.0": + version: 3.0.0 + resolution: "@smithy/property-provider@npm:3.0.0" + dependencies: + "@smithy/types": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: 37c9b949f0df60240b51a4a6e60a772e4ffc5f50de7fb51c74aec4336f46ba7c424f81181487ba6c7a15b5a43f13d82f7609836e96cfc61728e1c26425a5a2b4 + languageName: node + linkType: hard + +"@smithy/protocol-http@npm:^4.0.0": + version: 4.0.0 + resolution: "@smithy/protocol-http@npm:4.0.0" + dependencies: + "@smithy/types": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: 0e663013be49ca6867e4d03d2759bae5a72653918617a0184c0f7ecf84043ebaf0f3e6a174f7f6f81934720f90bfce89cecc56510d572cd8d93f423483b74d93 + languageName: node + linkType: hard + +"@smithy/querystring-builder@npm:^3.0.0": + version: 3.0.0 + resolution: "@smithy/querystring-builder@npm:3.0.0" + dependencies: + "@smithy/types": "npm:^3.0.0" + "@smithy/util-uri-escape": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: bca3e4c32127f444d7d7812c6afc6cc0dadbbd52a6359f09bf4ba04d2a7f6a09395f61c981b4cf64d714e6010a93ba4a729989f869e4cc32c065aca86bd8f2fc + languageName: node + linkType: hard + +"@smithy/querystring-parser@npm:^3.0.0": + version: 3.0.0 + resolution: "@smithy/querystring-parser@npm:3.0.0" + dependencies: + "@smithy/types": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: c0258dd552819ffd584abc858d702428da4d6d850eeaa47b29bd15972d428e5b6d62cc9a6609c83ad58e1fedcc38a9189093568163eac6ecf24ea38a96e31779 + languageName: node + linkType: hard + +"@smithy/service-error-classification@npm:^3.0.0": + version: 3.0.0 + resolution: "@smithy/service-error-classification@npm:3.0.0" + dependencies: + "@smithy/types": "npm:^3.0.0" + checksum: b7922ac401773fe4ff500378d8731e9fe8b7dceb6707a48ea93051c0158f2cec7195c718dd80b940af57ef584e36982792f1fe7d31d52c4173c1c495775075a0 + languageName: node + linkType: hard + +"@smithy/shared-ini-file-loader@npm:^3.0.0": + version: 3.0.0 + resolution: "@smithy/shared-ini-file-loader@npm:3.0.0" + dependencies: + "@smithy/types": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: 29b2fda4aa6a5688453dd025a1acf867461c9b59db52998e2bac41b7acca8aea45aa41b275cfac27443446a90e9e22da794fb7fd64c2a244cdce80e0c373237f + languageName: node + linkType: hard + +"@smithy/signature-v4@npm:^3.0.0": + version: 3.0.0 + resolution: "@smithy/signature-v4@npm:3.0.0" + dependencies: + "@smithy/is-array-buffer": "npm:^3.0.0" + "@smithy/types": "npm:^3.0.0" + "@smithy/util-hex-encoding": "npm:^3.0.0" + "@smithy/util-middleware": "npm:^3.0.0" + "@smithy/util-uri-escape": "npm:^3.0.0" + "@smithy/util-utf8": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: 528461766bc6a941216a17331ef61ecc72a2e0171b10c6b40bfafb33e3c83a77f1003541a9986a3c5b61320cc28c95c2aff7c3fa650c6e70a62cb765327e9a9e + languageName: node + linkType: hard + +"@smithy/smithy-client@npm:^3.0.0, @smithy/smithy-client@npm:^3.0.1": + version: 3.0.1 + resolution: "@smithy/smithy-client@npm:3.0.1" + dependencies: + "@smithy/middleware-endpoint": "npm:^3.0.0" + "@smithy/middleware-stack": "npm:^3.0.0" + "@smithy/protocol-http": "npm:^4.0.0" + "@smithy/types": "npm:^3.0.0" + "@smithy/util-stream": "npm:^3.0.1" + tslib: "npm:^2.6.2" + checksum: c9813aa7de2b11d4eb93482b42a52467d1b1fa94e18678ed343ecdb9929880c7526722c22e68993b9f238763cf43e21f266e7c51d3041a93ebaba1112e27ac0f + languageName: node + linkType: hard + +"@smithy/types@npm:^3.0.0": + version: 3.0.0 + resolution: "@smithy/types@npm:3.0.0" + dependencies: + tslib: "npm:^2.6.2" + checksum: 8b9a45fc24e2e9702bc9614facbb7ad7c5b3b7a7b438afeeae770e25e62182827e3ea24367e466705f25e4f83e89ff89d0acbcd4c42195fba847821b649205db + languageName: node + linkType: hard + +"@smithy/url-parser@npm:^3.0.0": + version: 3.0.0 + resolution: "@smithy/url-parser@npm:3.0.0" + dependencies: + "@smithy/querystring-parser": "npm:^3.0.0" + "@smithy/types": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: f88c1a2537593dd8c9643d42fbfde313c630bbb3f2dc9d202d58df298504534c4cedc4595173b1a290ada9220c97096d2653eed9024a00053a08452621db3a9a + languageName: node + linkType: hard + +"@smithy/util-base64@npm:^3.0.0": + version: 3.0.0 + resolution: "@smithy/util-base64@npm:3.0.0" + dependencies: + "@smithy/util-buffer-from": "npm:^3.0.0" + "@smithy/util-utf8": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: 3c883d63a33e1cfeebf7f846f167dfc54c832d1f5514d014fbfff06de3aecd5919f01637fc93668dca8a1029752f3a6fab0a94f455dcb7c88f1d472bde294eef + languageName: node + linkType: hard + +"@smithy/util-body-length-browser@npm:^3.0.0": + version: 3.0.0 + resolution: "@smithy/util-body-length-browser@npm:3.0.0" + dependencies: + tslib: "npm:^2.6.2" + checksum: a0ab6a6d414a62d18e57f3581769379a54bb1fd93606c69bc8d96a3566fdecb8db7b57da9446568d03eef9f004f2a89d7e94bdda79ef280f28b19a71803c0309 + languageName: node + linkType: hard + +"@smithy/util-body-length-node@npm:^3.0.0": + version: 3.0.0 + resolution: "@smithy/util-body-length-node@npm:3.0.0" + dependencies: + tslib: "npm:^2.6.2" + checksum: aabac66d7111612fd375d67150f8787c5cdc828f7e6acb40ef0b18b4c354e64e0ef2e4b8da2d7f01e8abe931ff2ef8109a408164ce7e5662fd41b470c462f1e4 + languageName: node + linkType: hard + +"@smithy/util-buffer-from@npm:^3.0.0": + version: 3.0.0 + resolution: "@smithy/util-buffer-from@npm:3.0.0" + dependencies: + "@smithy/is-array-buffer": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: 7e6596b38855c07869f7e8f7b0ad9b70c5e658f4a06c7db71c6134a9a785ac1fdaa84f8b3358c4a572767838498df118daad1fa937237d1fb4b9fce735cf8bb0 + languageName: node + linkType: hard + +"@smithy/util-config-provider@npm:^3.0.0": + version: 3.0.0 + resolution: "@smithy/util-config-provider@npm:3.0.0" + dependencies: + tslib: "npm:^2.6.2" + checksum: 614c321c5a5a220d7d72d36359c41b4e390566719f7e01cefebffbe7034aae78b4533b27ab2030f93186c5f22893ddf056a3a2376a077d70ce89275f31e1ac46 + languageName: node + linkType: hard + +"@smithy/util-defaults-mode-browser@npm:^3.0.0": + version: 3.0.1 + resolution: "@smithy/util-defaults-mode-browser@npm:3.0.1" + dependencies: + "@smithy/property-provider": "npm:^3.0.0" + "@smithy/smithy-client": "npm:^3.0.1" + "@smithy/types": "npm:^3.0.0" + bowser: "npm:^2.11.0" + tslib: "npm:^2.6.2" + checksum: 87ce3e6d9a935fa19b8a99bb91739093679a6cf2fbd2167b324f24da07a00120b4f2206c709261eddacc5c681a0096a53ceadbf446d0691f44a3651097794f43 + languageName: node + linkType: hard + +"@smithy/util-defaults-mode-node@npm:^3.0.0": + version: 3.0.1 + resolution: "@smithy/util-defaults-mode-node@npm:3.0.1" + dependencies: + "@smithy/config-resolver": "npm:^3.0.0" + "@smithy/credential-provider-imds": "npm:^3.0.0" + "@smithy/node-config-provider": "npm:^3.0.0" + "@smithy/property-provider": "npm:^3.0.0" + "@smithy/smithy-client": "npm:^3.0.1" + "@smithy/types": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: cda4d67f0b9d72278488e06d1f1d003e3660273fcb4bbe98ce6436adc37e300858773cc3d55689bad481699aaf123c728c06280af5307928500ff3ea5180cc79 + languageName: node + linkType: hard + +"@smithy/util-endpoints@npm:^2.0.0": + version: 2.0.0 + resolution: "@smithy/util-endpoints@npm:2.0.0" + dependencies: + "@smithy/node-config-provider": "npm:^3.0.0" + "@smithy/types": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: 49e897e8b1c19a79f71edfa1b5fa58f90b3244e5026e38c32c3bd2ff2672f4a2de9dbb0c0cf7dfaf8ae6de25db3c8ea76cfbbfc0db8415935721863bcda527bd + languageName: node + linkType: hard + +"@smithy/util-hex-encoding@npm:^3.0.0": + version: 3.0.0 + resolution: "@smithy/util-hex-encoding@npm:3.0.0" + dependencies: + tslib: "npm:^2.6.2" + checksum: d9f8c4c676410ca51bdbcec5d986883bad0028e26b098fc50e2b57bc81e8a5ce20e160786d08c8552ca0ba662c88ca16f33857ff24a0d183174325b2b40e3c8f + languageName: node + linkType: hard + +"@smithy/util-middleware@npm:^3.0.0": + version: 3.0.0 + resolution: "@smithy/util-middleware@npm:3.0.0" + dependencies: + "@smithy/types": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: e9878f85326859b8025df7e2cf7aba5b9fb8ec59be2189c61b0082947c967d888d6894ce6e2152a28eda3e03c207453a94fba7dbf084d755e2ada2df5a58cbb5 + languageName: node + linkType: hard + +"@smithy/util-retry@npm:^3.0.0": + version: 3.0.0 + resolution: "@smithy/util-retry@npm:3.0.0" + dependencies: + "@smithy/service-error-classification": "npm:^3.0.0" + "@smithy/types": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: 9e38115e47f99bd86360864ed4dd84a266a4e8bc528c6cc834760339cfef857263eb557b85e060776775c1a70b0f03ded0133b9b23c31095d41e51d247a2b1a3 + languageName: node + linkType: hard + +"@smithy/util-stream@npm:^3.0.0, @smithy/util-stream@npm:^3.0.1": + version: 3.0.1 + resolution: "@smithy/util-stream@npm:3.0.1" + dependencies: + "@smithy/fetch-http-handler": "npm:^3.0.1" + "@smithy/node-http-handler": "npm:^3.0.0" + "@smithy/types": "npm:^3.0.0" + "@smithy/util-base64": "npm:^3.0.0" + "@smithy/util-buffer-from": "npm:^3.0.0" + "@smithy/util-hex-encoding": "npm:^3.0.0" + "@smithy/util-utf8": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: da1d1a6d3ccb5d27e117d6d9331ba7a74501150eecda6cd4625f6a615ff3388f805ee8dd87366edd16fbf335f817cb27f7d4655c6629d10025240952a77913b0 + languageName: node + linkType: hard + +"@smithy/util-uri-escape@npm:^3.0.0": + version: 3.0.0 + resolution: "@smithy/util-uri-escape@npm:3.0.0" + dependencies: + tslib: "npm:^2.6.2" + checksum: d44522339325b0f1fe2c5bf1a3f01d5a699eb8718d800dee24378a1a1b301683756dcfd4be4c32db4d6a00cad85893494778ae39fb246a03aef27d06c9852a67 + languageName: node + linkType: hard + +"@smithy/util-utf8@npm:^3.0.0": + version: 3.0.0 + resolution: "@smithy/util-utf8@npm:3.0.0" + dependencies: + "@smithy/util-buffer-from": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: 1aead297d835af419f75e0c3113c021aa0da671110d1b498035530d5f35d8030092cad5147edaa7ca458aafe27c9383399ccd8176d342942465a2d8357e5cbf4 + languageName: node + linkType: hard + +"@smithy/util-waiter@npm:^3.0.0": + version: 3.0.0 + resolution: "@smithy/util-waiter@npm:3.0.0" + dependencies: + "@smithy/abort-controller": "npm:^3.0.0" + "@smithy/types": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: d206c9f6613e1c43675a48214dd762cb7f85ba57182d2dbcff80392a1983a7f6b06bd537c89949017100bf641d71a32d0c62299d172c52480240c5a431b797ac + languageName: node + linkType: hard + "@solana/buffer-layout-utils@npm:^0.2.0": version: 0.2.0 resolution: "@solana/buffer-layout-utils@npm:0.2.0" @@ -10001,7 +11303,7 @@ __metadata: languageName: node linkType: hard -"asn1.js@npm:5.4.1": +"asn1.js@npm:5.4.1, asn1.js@npm:^5.4.1": version: 5.4.1 resolution: "asn1.js@npm:5.4.1" dependencies: @@ -13748,6 +15050,17 @@ __metadata: languageName: node linkType: hard +"fast-xml-parser@npm:4.2.5": + version: 4.2.5 + resolution: "fast-xml-parser@npm:4.2.5" + dependencies: + strnum: "npm:^1.0.5" + bin: + fxparser: src/cli/cli.js + checksum: 4be7ebe24d6a9a60c278e1423cd86a7da9a77ec64c95563e2c552363caf7a777e0c87c9de1255c2f4e8dea9bce8905dc2bdc58a34e9f2b73c4693654456ad284 + languageName: node + linkType: hard + "fastq@npm:^1.6.0": version: 1.13.0 resolution: "fastq@npm:1.13.0" @@ -22370,6 +23683,13 @@ __metadata: languageName: node linkType: hard +"strnum@npm:^1.0.5": + version: 1.0.5 + resolution: "strnum@npm:1.0.5" + checksum: d3117975db8372d4d7b2c07601ed2f65bf21cc48d741f37a8617b76370d228f2ec26336e53791ebc3638264d23ca54e6c241f57f8c69bd4941c63c79440525ca + languageName: node + linkType: hard + "styled-jsx@npm:5.1.1": version: 5.1.1 resolution: "styled-jsx@npm:5.1.1" @@ -22947,7 +24267,7 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^2.0.0": +"tslib@npm:^2.0.0, tslib@npm:^2.6.2": version: 2.6.2 resolution: "tslib@npm:2.6.2" checksum: bd26c22d36736513980091a1e356378e8b662ded04204453d353a7f34a4c21ed0afc59b5f90719d4ba756e581a162ecbf93118dc9c6be5acf70aa309188166ca @@ -23729,6 +25049,15 @@ __metadata: languageName: node linkType: hard +"uuid@npm:^9.0.1": + version: 9.0.1 + resolution: "uuid@npm:9.0.1" + bin: + uuid: dist/bin/uuid + checksum: 9d0b6adb72b736e36f2b1b53da0d559125ba3e39d913b6072f6f033e0c87835b414f0836b45bcfaf2bdf698f92297fea1c3cc19b0b258bc182c9c43cc0fab9f2 + languageName: node + linkType: hard + "v8-compile-cache-lib@npm:^3.0.1": version: 3.0.1 resolution: "v8-compile-cache-lib@npm:3.0.1" From a0c46299a39e99852f7165ef0435c2ee1862b379 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Wed, 29 May 2024 16:39:31 -0400 Subject: [PATCH 06/73] fix: typo in core module (#3865) --- typescript/sdk/src/core/EvmCoreModule.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typescript/sdk/src/core/EvmCoreModule.ts b/typescript/sdk/src/core/EvmCoreModule.ts index f4dd3ee10..113a15248 100644 --- a/typescript/sdk/src/core/EvmCoreModule.ts +++ b/typescript/sdk/src/core/EvmCoreModule.ts @@ -119,7 +119,7 @@ export class EvmCoreModule extends HyperlaneModule< multiProvider, ); - // Initalize Deployer + // Initialize Deployer const coreDeployer = new HyperlaneCoreDeployer(multiProvider, ismFactory); // Deploy proxyAdmin From 876e1cf9287eae77dbaa8f2d9f7f5e143394770b Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Wed, 29 May 2024 16:55:28 -0400 Subject: [PATCH 07/73] chore: run cli-e2e on cli-2.0 branch (#3864) --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b6bb18d5f..e56f11f58 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -325,7 +325,7 @@ jobs: cli-e2e: runs-on: larger-runner - if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.base_ref == 'main') + if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.base_ref == 'main' || github.base_ref == 'cli-2.0') needs: [yarn-build, checkout-registry] strategy: matrix: From 1ec61debd2425785ca4ade111ad3cdaba8ccc8ac Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Thu, 30 May 2024 16:40:45 -0400 Subject: [PATCH 08/73] feat: support hook config objects in warp config (#3859) Fixes https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/3728 Fixes https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/3667 --- .changeset/brave-humans-draw.md | 7 + typescript/cli/examples/hooks.yaml | 12 +- typescript/cli/src/config/hooks.ts | 72 ++------ typescript/cli/src/config/ism.ts | 162 ++++++------------ typescript/cli/src/deploy/core.ts | 14 +- typescript/cli/src/tests/hooks.test.ts | 31 ++-- .../cli/src/tests/hooks/safe-parse-fail.yaml | 4 - typescript/cli/src/tests/ism.test.ts | 4 +- typescript/helloworld/src/deploy/deploy.ts | 3 +- .../config/environments/mainnet3/core.ts | 8 +- .../infra/config/environments/mainnet3/igp.ts | 39 +++-- .../infra/config/environments/test/core.ts | 6 +- .../infra/config/environments/test/igp.ts | 52 +++--- .../config/environments/testnet4/core.ts | 8 +- .../infra/config/environments/testnet4/igp.ts | 41 +++-- typescript/infra/scripts/check-deploy.ts | 7 +- typescript/infra/scripts/deploy.ts | 8 +- typescript/infra/scripts/helloworld/kathy.ts | 12 +- typescript/infra/test/govern.hardhat-test.ts | 7 +- .../sdk/src/core/CoreDeployer.hardhat-test.ts | 22 ++- .../sdk/src/core/HyperlaneCoreDeployer.ts | 12 +- typescript/sdk/src/core/schemas.ts | 9 + typescript/sdk/src/core/types.ts | 12 +- .../sdk/src/deploy/HyperlaneAppChecker.ts | 13 +- .../sdk/src/deploy/HyperlaneDeployer.ts | 41 ++--- typescript/sdk/src/deploy/types.ts | 38 +--- .../sdk/src/gas/HyperlaneIgpDeployer.ts | 15 +- .../configure-gas-oracles.hardhat-test.ts | 14 +- typescript/sdk/src/gas/oracle/types.ts | 34 ++-- typescript/sdk/src/gas/types.ts | 20 +-- typescript/sdk/src/hook/EvmHookReader.test.ts | 4 + typescript/sdk/src/hook/EvmHookReader.ts | 9 +- .../sdk/src/hook/HyperlaneHookDeployer.ts | 39 +++-- typescript/sdk/src/hook/schemas.ts | 78 +++++++++ typescript/sdk/src/hook/types.ts | 62 ++----- typescript/sdk/src/index.ts | 12 +- typescript/sdk/src/ism/EvmIsmCreator.ts | 24 +-- typescript/sdk/src/ism/HyperlaneIsmFactory.ts | 23 +-- typescript/sdk/src/ism/metadata/builder.ts | 3 + typescript/sdk/src/ism/schemas.ts | 71 ++++---- typescript/sdk/src/ism/types.ts | 58 +++---- typescript/sdk/src/ism/utils.ts | 19 +- .../account/InterchainAccountDeployer.ts | 10 +- .../sdk/src/router/HyperlaneRouterChecker.ts | 140 ++++++--------- .../sdk/src/router/ProxiedRouterDeployer.ts | 7 +- typescript/sdk/src/router/schemas.ts | 7 +- typescript/sdk/src/schemas.ts | 12 ++ typescript/sdk/src/test/testUtils.ts | 5 +- .../sdk/src/token/EvmERC20WarpRouteReader.ts | 7 +- .../sdk/src/token/deploy.hardhat-test.ts | 9 +- 50 files changed, 590 insertions(+), 736 deletions(-) create mode 100644 .changeset/brave-humans-draw.md create mode 100644 typescript/sdk/src/core/schemas.ts create mode 100644 typescript/sdk/src/hook/schemas.ts create mode 100644 typescript/sdk/src/schemas.ts diff --git a/.changeset/brave-humans-draw.md b/.changeset/brave-humans-draw.md new file mode 100644 index 000000000..4548de7de --- /dev/null +++ b/.changeset/brave-humans-draw.md @@ -0,0 +1,7 @@ +--- +'@hyperlane-xyz/infra': minor +'@hyperlane-xyz/cli': minor +'@hyperlane-xyz/sdk': minor +--- + +Support hook config objects in warp config diff --git a/typescript/cli/examples/hooks.yaml b/typescript/cli/examples/hooks.yaml index 9fc19433a..cc5fbec92 100644 --- a/typescript/cli/examples/hooks.yaml +++ b/typescript/cli/examples/hooks.yaml @@ -39,8 +39,10 @@ anvil1: oracleKey: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' overhead: anvil2: 50000 # gas amount (number) - gasOracleType: - anvil2: StorageGasOracle + oracleConfig: + anvil2: + gasPrice: '1000000000000000000' + tokenExchangeRate: '1000000000000000000' anvil2: required: type: protocolFee @@ -62,5 +64,7 @@ anvil2: oracleKey: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' overhead: anvil1: 50000 - gasOracleType: - anvil1: StorageGasOracle + oracleConfig: + anvil1: + gasPrice: '1000000000000000000' + tokenExchangeRate: '1000000000000000000' diff --git a/typescript/cli/src/config/hooks.ts b/typescript/cli/src/config/hooks.ts index aeb449dec..d7fb61979 100644 --- a/typescript/cli/src/config/hooks.ts +++ b/typescript/cli/src/config/hooks.ts @@ -6,9 +6,9 @@ import { z } from 'zod'; import { ChainMap, ChainName, - GasOracleContractType, + HookConfig, + HookConfigSchema, HookType, - HooksConfig, } from '@hyperlane-xyz/sdk'; import { Address, @@ -22,55 +22,12 @@ import { errorRed, log, logBlue, logGreen, logRed } from '../logger.js'; import { runMultiChainSelectionStep } from '../utils/chains.js'; import { mergeYamlOrJson, readYamlOrJson } from '../utils/files.js'; -const ProtocolFeeSchema = z.object({ - type: z.literal(HookType.PROTOCOL_FEE), - owner: z.string(), - beneficiary: z.string(), - maxProtocolFee: z.string(), - protocolFee: z.string(), -}); - -const MerkleTreeSchema = z.object({ - type: z.literal(HookType.MERKLE_TREE), -}); - -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; - +// TODO: deprecate in favor of CoreConfigSchema const HooksConfigSchema = z.object({ - required: HookConfigSchema, default: HookConfigSchema, + required: HookConfigSchema, }); +export type HooksConfig = z.infer; const HooksConfigMapSchema = z.record(HooksConfigSchema); export type HooksConfigMap = z.infer; @@ -99,14 +56,7 @@ export function readHooksConfigMap(filePath: string) { logRed(`No hook config found at ${filePath}`); return; } - const result = HooksConfigMapSchema.safeParse(config); - if (!result.success) { - const firstIssue = result.error.issues[0]; - throw new Error( - `Invalid hook config: ${firstIssue.path} => ${firstIssue.message}`, - ); - } - const parsedConfig = result.data; + const parsedConfig = HooksConfigMapSchema.parse(config); const hooks: ChainMap = objMap( parsedConfig, (_, config) => config as HooksConfig, @@ -192,8 +142,8 @@ export async function createHookConfig( lastConfig = { type: HookType.MERKLE_TREE }; } else if (hookType === HookType.PROTOCOL_FEE) { lastConfig = await createProtocolFeeConfig(context, chain); - } else if (hookType === HookType.INTERCHAIN_GAS_PAYMASTER) { - lastConfig = await createIGPConfig(remotes); + // } else if (hookType === HookType.INTERCHAIN_GAS_PAYMASTER) { + // lastConfig = await createIGPConfig(remotes); } else if (hookType === HookType.AGGREGATION) { lastConfig = await createAggregationConfig(context, chain, remotes); } else if (hookType === HookType.ROUTING) { @@ -256,6 +206,7 @@ export async function createProtocolFeeConfig( }; } +// TODO: make this usable export async function createIGPConfig( remotes: ChainName[], ): Promise { @@ -296,10 +247,7 @@ export async function createIGPConfig( owner: ownerAddress, oracleKey: oracleKeyAddress, overhead: overheads, - gasOracleType: objMap( - overheads, - () => GasOracleContractType.StorageGasOracle, - ), + oracleConfig: {}, }; } diff --git a/typescript/cli/src/config/ism.ts b/typescript/cli/src/config/ism.ts index 66075c493..17780f2ad 100644 --- a/typescript/cli/src/config/ism.ts +++ b/typescript/cli/src/config/ism.ts @@ -1,7 +1,16 @@ import { confirm, input, select } from '@inquirer/prompts'; import { z } from 'zod'; -import { ChainMap, ChainName, IsmType, ZHash } from '@hyperlane-xyz/sdk'; +import { + AggregationIsmConfig, + ChainMap, + ChainName, + IsmConfig, + IsmConfigSchema, + IsmType, + MultisigIsmConfig, + TrustedRelayerIsmConfig, +} from '@hyperlane-xyz/sdk'; import { CommandContext } from '../context/types.js'; import { @@ -15,64 +24,14 @@ import { import { runMultiChainSelectionStep } from '../utils/chains.js'; import { mergeYamlOrJson, readYamlOrJson } from '../utils/files.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 TrustedRelayerIsmConfigSchema = z.object({ - type: z.literal(IsmType.TRUSTED_RELAYER), - relayer: ZHash, -}); - -const IsmConfigSchema = z.union([ - MultisigIsmConfigSchema, - RoutingIsmConfigSchema, - AggregationIsmConfigSchema, - TestIsmConfigSchema, - TrustedRelayerIsmConfigSchema, -]); 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 (typeof config === 'string') { + continue; + } + if (config.type === IsmType.ROUTING) { if (config.domains && key in config.domains) { return false; @@ -86,8 +45,8 @@ const IsmConfigMapSchema = z.record(IsmConfigSchema).refine( 'Cannot set RoutingIsm.domain to the same chain you are configuring', }, ); -export type ZodIsmConfig = z.infer; -export type ZodIsmConfigMap = z.infer; + +type IsmConfigMap = z.infer; export function parseIsmConfig(filePath: string) { const config = readYamlOrJson(filePath); @@ -129,7 +88,7 @@ export async function createIsmConfigMap({ true, ); - const result: ZodIsmConfigMap = {}; + const result: IsmConfigMap = {}; for (const chain of chains) { log(`Setting values for chain ${chain}`); result[chain] = await createIsmConfig(chain, chains); @@ -151,75 +110,60 @@ export async function createIsmConfigMap({ } } +const ISM_TYPE_DESCRIPTIONS: Record = { + [IsmType.MESSAGE_ID_MULTISIG]: 'Validators need to sign just this messageId', + [IsmType.MERKLE_ROOT_MULTISIG]: + 'Validators need to sign the root of the merkle tree of all messages from origin chain', + [IsmType.ROUTING]: + 'Each origin chain can be verified by the specified ISM type via RoutingISM', + [IsmType.FALLBACK_ROUTING]: + "You can specify ISM type for specific chains you like and fallback to mailbox's default ISM for other chains via DefaultFallbackRoutingISM", + [IsmType.AGGREGATION]: + 'You can aggregate multiple ISMs into one ISM via AggregationISM', + [IsmType.TRUSTED_RELAYER]: 'Deliver messages from an authorized address', + [IsmType.TEST_ISM]: + 'ISM where you can deliver messages without any validation (WARNING: only for testing, do not use in production)', + [IsmType.OP_STACK]: '', + [IsmType.PAUSABLE]: '', +}; + export async function createIsmConfig( remote: ChainName, origins: ChainName[], -): Promise { - let lastConfig: ZodIsmConfig; +): Promise { const moduleType = await select({ message: 'Select ISM type', - choices: [ - { - value: IsmType.MESSAGE_ID_MULTISIG, - description: 'Validators need to sign just this messageId', - }, - { - value: IsmType.MERKLE_ROOT_MULTISIG, - description: - 'Validators need to sign the root of the merkle tree of all messages from origin chain', - }, - { - value: IsmType.ROUTING, - description: - 'Each origin chain can be verified by the specified ISM type via RoutingISM', - }, - { - value: 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, - description: - 'You can aggregate multiple ISMs into one ISM via AggregationISM', - }, - { - value: IsmType.TRUSTED_RELAYER, - description: 'Deliver messages from an authorized address', - }, - { - value: IsmType.TEST_ISM, - description: - 'ISM where you can deliver messages without any validation (WARNING: only for testing, do not use in production)', - }, - ], + choices: Object.values(IsmType).map((value) => ({ + value, + description: ISM_TYPE_DESCRIPTIONS[value], + })), pageSize: 10, }); + if ( moduleType === IsmType.MESSAGE_ID_MULTISIG || moduleType === IsmType.MERKLE_ROOT_MULTISIG ) { - lastConfig = await createMultisigConfig(moduleType); + return createMultisigConfig(moduleType); } else if ( moduleType === IsmType.ROUTING || moduleType === IsmType.FALLBACK_ROUTING ) { - lastConfig = await createRoutingConfig(moduleType, remote, origins); + return createRoutingConfig(moduleType, remote, origins); } else if (moduleType === IsmType.AGGREGATION) { - lastConfig = await createAggregationConfig(remote, origins); + return createAggregationConfig(remote, origins); } else if (moduleType === IsmType.TEST_ISM) { - lastConfig = { type: IsmType.TEST_ISM }; + return { type: IsmType.TEST_ISM }; } else if (moduleType === IsmType.TRUSTED_RELAYER) { - lastConfig = await createTrustedRelayerConfig(); - } else { - throw new Error(`Invalid ISM type: ${moduleType}}`); + return createTrustedRelayerConfig(); } - return lastConfig; + + throw new Error(`Invalid ISM type: ${moduleType}}`); } export async function createMultisigConfig( type: IsmType.MERKLE_ROOT_MULTISIG | IsmType.MESSAGE_ID_MULTISIG, -): Promise { +): Promise { const thresholdInput = await input({ message: 'Enter threshold of validators (number)', }); @@ -236,7 +180,7 @@ export async function createMultisigConfig( }; } -async function createTrustedRelayerConfig(): Promise { +async function createTrustedRelayerConfig(): Promise { const relayer = await input({ message: 'Enter relayer address', }); @@ -249,7 +193,7 @@ async function createTrustedRelayerConfig(): Promise { export async function createAggregationConfig( remote: ChainName, chains: ChainName[], -): Promise { +): Promise { const isms = parseInt( await input({ message: 'Enter the number of ISMs to aggregate (number)', @@ -264,7 +208,7 @@ export async function createAggregationConfig( 10, ); - const modules: Array = []; + const modules: Array = []; for (let i = 0; i < isms; i++) { modules.push(await createIsmConfig(remote, chains)); } @@ -279,14 +223,14 @@ export async function createRoutingConfig( type: IsmType.ROUTING | IsmType.FALLBACK_ROUTING, remote: ChainName, chains: ChainName[], -): Promise { +): Promise { const owner = await input({ message: 'Enter owner address', }); const ownerAddress = owner; const origins = chains.filter((chain) => chain !== remote); - const domainsMap: ChainMap = {}; + const domainsMap: ChainMap = {}; for (const chain of origins) { await confirm({ message: `You are about to configure ISM from source chain ${chain}. Continue?`, diff --git a/typescript/cli/src/deploy/core.ts b/typescript/cli/src/deploy/core.ts index 8f8bf2043..54edc8055 100644 --- a/typescript/cli/src/deploy/core.ts +++ b/typescript/cli/src/deploy/core.ts @@ -6,8 +6,7 @@ import { ChainMap, ChainName, CoreConfig, - GasOracleContractType, - HooksConfig, + HookType, HyperlaneAddressesMap, HyperlaneContractsMap, HyperlaneCore, @@ -27,7 +26,11 @@ import { } from '@hyperlane-xyz/sdk'; import { Address, objFilter, objMap, objMerge } from '@hyperlane-xyz/utils'; -import { presetHookConfigs, readHooksConfigMap } from '../config/hooks.js'; +import { + HooksConfig, + 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'; @@ -370,7 +373,6 @@ export function buildIgpConfigMap( const configMap: ChainMap = {}; for (const chain of chains) { const overhead: ChainMap = {}; - const gasOracleType: ChainMap = {}; for (const remote of chains) { if (chain === remote) continue; // TODO: accurate estimate of gas from ChainMap @@ -384,14 +386,14 @@ export function buildIgpConfigMap( threshold, validatorsLength, ); - gasOracleType[remote] = GasOracleContractType.StorageGasOracle; } configMap[chain] = { + type: HookType.INTERCHAIN_GAS_PAYMASTER, owner, beneficiary: owner, - gasOracleType, overhead, oracleKey: owner, + oracleConfig: {}, }; } return configMap; diff --git a/typescript/cli/src/tests/hooks.test.ts b/typescript/cli/src/tests/hooks.test.ts index 2848c0c06..0401ce024 100644 --- a/typescript/cli/src/tests/hooks.test.ts +++ b/typescript/cli/src/tests/hooks.test.ts @@ -1,19 +1,14 @@ import { expect } from 'chai'; -import { - ChainMap, - GasOracleContractType, - HookType, - HooksConfig, -} from '@hyperlane-xyz/sdk'; +import { HookType } from '@hyperlane-xyz/sdk'; -import { readHooksConfigMap } from '../config/hooks.js'; +import { HooksConfigMap, readHooksConfigMap } from '../config/hooks.js'; describe('readHooksConfigMap', () => { it('parses and validates example correctly', () => { const hooks = readHooksConfigMap('examples/hooks.yaml'); - const exampleHooksConfig: ChainMap = { + const exampleHooksConfig: HooksConfigMap = { anvil1: { required: { type: HookType.PROTOCOL_FEE, @@ -37,10 +32,13 @@ describe('readHooksConfigMap', () => { beneficiary: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', owner: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', oracleKey: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', - gasOracleType: { - anvil2: GasOracleContractType.StorageGasOracle, - }, overhead: { anvil2: 50000 }, + oracleConfig: { + anvil2: { + gasPrice: '1000000000000000000', + tokenExchangeRate: '1000000000000000000', + }, + }, }, ], }, @@ -70,10 +68,13 @@ describe('readHooksConfigMap', () => { beneficiary: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', owner: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', oracleKey: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', - gasOracleType: { - anvil1: GasOracleContractType.StorageGasOracle, - }, overhead: { anvil1: 50000 }, + oracleConfig: { + anvil1: { + gasPrice: '1000000000000000000', + tokenExchangeRate: '1000000000000000000', + }, + }, }, ], }, @@ -87,6 +88,6 @@ describe('readHooksConfigMap', () => { 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'); + }).to.throw(); }); }); diff --git a/typescript/cli/src/tests/hooks/safe-parse-fail.yaml b/typescript/cli/src/tests/hooks/safe-parse-fail.yaml index 4a2a5cedb..7257817d4 100644 --- a/typescript/cli/src/tests/hooks/safe-parse-fail.yaml +++ b/typescript/cli/src/tests/hooks/safe-parse-fail.yaml @@ -19,8 +19,6 @@ anvil1: oracleKey: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' overhead: anvil2: 50000 - gasOracleType: - anvil2: StorageGasOracle anvil2: required: type: protocolFee @@ -40,5 +38,3 @@ anvil2: 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 index 4942963cf..04d84caa9 100644 --- a/typescript/cli/src/tests/ism.test.ts +++ b/typescript/cli/src/tests/ism.test.ts @@ -80,6 +80,8 @@ describe('readIsmConfig', () => { 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'); + }).to.throw( + 'Threshold must be less than or equal to the number of modules', + ); }); }); diff --git a/typescript/helloworld/src/deploy/deploy.ts b/typescript/helloworld/src/deploy/deploy.ts index b7dccecda..8fcbc6185 100644 --- a/typescript/helloworld/src/deploy/deploy.ts +++ b/typescript/helloworld/src/deploy/deploy.ts @@ -38,8 +38,9 @@ export class HelloWorldDeployer extends HyperlaneRouterDeployer< async deployContracts(chain: ChainName, config: HelloWorldConfig) { const router = await this.deployContract(chain, 'router', [ config.mailbox, - config.hook ?? ethers.constants.AddressZero, + ethers.constants.AddressZero, ]); + await super.configureClient(chain, router, config); return { router, }; diff --git a/typescript/infra/config/environments/mainnet3/core.ts b/typescript/infra/config/environments/mainnet3/core.ts index 258b37c1c..e2a218662 100644 --- a/typescript/infra/config/environments/mainnet3/core.ts +++ b/typescript/infra/config/environments/mainnet3/core.ts @@ -7,7 +7,6 @@ import { CoreConfig, FallbackRoutingHookConfig, HookType, - IgpHookConfig, IsmType, MerkleTreeHookConfig, MultisigConfig, @@ -59,6 +58,7 @@ export const core: ChainMap = objMap(owners, (local, owner) => { const pausableIsm: PausableIsmConfig = { type: IsmType.PAUSABLE, + paused: false, owner: DEPLOYER, // keep pausable hot }; @@ -72,13 +72,11 @@ export const core: ChainMap = objMap(owners, (local, owner) => { type: HookType.MERKLE_TREE, }; - const igpHook: IgpHookConfig = { - type: HookType.INTERCHAIN_GAS_PAYMASTER, - ...igp[local], - }; + const igpHook = igp[local]; const pausableHook: PausableHookConfig = { type: HookType.PAUSABLE, + paused: false, owner: DEPLOYER, // keep pausable hot }; const aggregationHooks = objMap( diff --git a/typescript/infra/config/environments/mainnet3/igp.ts b/typescript/infra/config/environments/mainnet3/igp.ts index cc7fa27ee..89f083ee8 100644 --- a/typescript/infra/config/environments/mainnet3/igp.ts +++ b/typescript/infra/config/environments/mainnet3/igp.ts @@ -3,6 +3,7 @@ import { BigNumber, ethers } from 'ethers'; import { ChainMap, ChainName, + HookType, IgpConfig, TOKEN_EXCHANGE_RATE_DECIMALS, defaultMultisigConfigs, @@ -57,20 +58,24 @@ const storageGasOracleConfig: AllStorageGasOracleConfigs = (local) => remoteOverhead(local), ); -export const igp: ChainMap = objMap(owners, (local, owner) => ({ - ...owner, - ownerOverrides: { - ...owner.ownerOverrides, - interchainGasPaymaster: DEPLOYER, - storageGasOracle: DEPLOYER, - }, - oracleKey: DEPLOYER, - beneficiary: DEPLOYER, - overhead: Object.fromEntries( - exclude(local, supportedChainNames).map((remote) => [ - remote, - remoteOverhead(remote), - ]), - ), - oracleConfig: storageGasOracleConfig[local], -})); +export const igp: ChainMap = objMap( + owners, + (local, owner): IgpConfig => ({ + type: HookType.INTERCHAIN_GAS_PAYMASTER, + ...owner, + ownerOverrides: { + ...owner.ownerOverrides, + interchainGasPaymaster: DEPLOYER, + storageGasOracle: DEPLOYER, + }, + oracleKey: DEPLOYER, + beneficiary: DEPLOYER, + overhead: Object.fromEntries( + exclude(local, supportedChainNames).map((remote) => [ + remote, + remoteOverhead(remote), + ]), + ), + oracleConfig: storageGasOracleConfig[local], + }), +); diff --git a/typescript/infra/config/environments/test/core.ts b/typescript/infra/config/environments/test/core.ts index 6063d71c0..4e5988b80 100644 --- a/typescript/infra/config/environments/test/core.ts +++ b/typescript/infra/config/environments/test/core.ts @@ -6,7 +6,6 @@ import { CoreConfig, FallbackRoutingHookConfig, HookType, - IgpHookConfig, IsmType, MerkleTreeHookConfig, ProtocolFeeHookConfig, @@ -34,10 +33,7 @@ export const core: ChainMap = objMap(owners, (local, owner) => { type: HookType.MERKLE_TREE, }; - const igpHook: IgpHookConfig = { - type: HookType.INTERCHAIN_GAS_PAYMASTER, - ...igp[local], - }; + const igpHook = igp[local]; const aggregationHook: AggregationHookConfig = { type: HookType.AGGREGATION, diff --git a/typescript/infra/config/environments/test/igp.ts b/typescript/infra/config/environments/test/igp.ts index b47be48e6..e63e3de59 100644 --- a/typescript/infra/config/environments/test/igp.ts +++ b/typescript/infra/config/environments/test/igp.ts @@ -1,7 +1,6 @@ import { ChainMap, - ChainName, - GasOracleContractType, + HookType, IgpConfig, multisigIsmVerificationCost, } from '@hyperlane-xyz/sdk'; @@ -11,30 +10,25 @@ import { testChainNames } from './chains.js'; import { multisigIsm } from './multisigIsm.js'; import { owners } from './owners.js'; -function getGasOracles(local: ChainName) { - return Object.fromEntries( - exclude(local, testChainNames).map((name) => [ - name, - GasOracleContractType.StorageGasOracle, - ]), - ); -} - -export const igp: ChainMap = objMap(owners, (chain, ownerConfig) => { - const overhead = Object.fromEntries( - exclude(chain, testChainNames).map((remote) => [ - remote, - multisigIsmVerificationCost( - multisigIsm[remote].threshold, - multisigIsm[remote].validators.length, - ), - ]), - ); - return { - oracleKey: ownerConfig.owner as Address, // owner can be AccountConfig - beneficiary: ownerConfig.owner as Address, // same as above - gasOracleType: getGasOracles(chain), - overhead, - ...ownerConfig, - }; -}); +export const igp: ChainMap = objMap( + owners, + (chain, ownerConfig): IgpConfig => { + const overhead = Object.fromEntries( + exclude(chain, testChainNames).map((remote) => [ + remote, + multisigIsmVerificationCost( + multisigIsm[remote].threshold, + multisigIsm[remote].validators.length, + ), + ]), + ); + return { + type: HookType.INTERCHAIN_GAS_PAYMASTER, + oracleKey: ownerConfig.owner as Address, // owner can be AccountConfig + beneficiary: ownerConfig.owner as Address, // same as above + overhead, + oracleConfig: {}, + ...ownerConfig, + }; + }, +); diff --git a/typescript/infra/config/environments/testnet4/core.ts b/typescript/infra/config/environments/testnet4/core.ts index df52631f3..121bcc90e 100644 --- a/typescript/infra/config/environments/testnet4/core.ts +++ b/typescript/infra/config/environments/testnet4/core.ts @@ -7,7 +7,6 @@ import { CoreConfig, FallbackRoutingHookConfig, HookType, - IgpHookConfig, IsmType, MerkleTreeHookConfig, MultisigConfig, @@ -61,6 +60,7 @@ export const core: ChainMap = objMap( const pausableIsm: PausableIsmConfig = { type: IsmType.PAUSABLE, + paused: false, ...ownerConfig, }; @@ -74,13 +74,11 @@ export const core: ChainMap = objMap( type: HookType.MERKLE_TREE, }; - const igpHook: IgpHookConfig = { - type: HookType.INTERCHAIN_GAS_PAYMASTER, - ...igp[local], - }; + const igpHook = igp[local]; const pausableHook: PausableHookConfig = { type: HookType.PAUSABLE, + paused: false, ...ownerConfig, }; diff --git a/typescript/infra/config/environments/testnet4/igp.ts b/typescript/infra/config/environments/testnet4/igp.ts index f0736d46e..4c388b4f0 100644 --- a/typescript/infra/config/environments/testnet4/igp.ts +++ b/typescript/infra/config/environments/testnet4/igp.ts @@ -1,5 +1,6 @@ import { ChainMap, + HookType, IgpConfig, defaultMultisigConfigs, multisigIsmVerificationCost, @@ -10,21 +11,25 @@ import { storageGasOracleConfig } from './gas-oracle.js'; import { owners } from './owners.js'; import { supportedChainNames } from './supportedChainNames.js'; -export const igp: ChainMap = objMap(owners, (chain, ownerConfig) => { - return { - ...ownerConfig, - oracleKey: ownerConfig.owner as Address, - beneficiary: ownerConfig.owner as Address, - oracleConfig: storageGasOracleConfig[chain], - overhead: Object.fromEntries( - exclude(chain, supportedChainNames).map((remote) => [ - remote, - multisigIsmVerificationCost( - // TODO: parameterize this - defaultMultisigConfigs[remote].threshold, - defaultMultisigConfigs[remote].validators.length, - ), - ]), - ), - }; -}); +export const igp: ChainMap = objMap( + owners, + (chain, ownerConfig): IgpConfig => { + return { + type: HookType.INTERCHAIN_GAS_PAYMASTER, + ...ownerConfig, + oracleKey: ownerConfig.owner as Address, + beneficiary: ownerConfig.owner as Address, + oracleConfig: storageGasOracleConfig[chain], + overhead: Object.fromEntries( + exclude(chain, supportedChainNames).map((remote) => [ + remote, + multisigIsmVerificationCost( + // TODO: parameterize this + defaultMultisigConfigs[remote].threshold, + defaultMultisigConfigs[remote].validators.length, + ), + ]), + ), + }; + }, +); diff --git a/typescript/infra/scripts/check-deploy.ts b/typescript/infra/scripts/check-deploy.ts index 47160c889..8ed806e74 100644 --- a/typescript/infra/scripts/check-deploy.ts +++ b/typescript/infra/scripts/check-deploy.ts @@ -10,7 +10,6 @@ import { InterchainAccountChecker, InterchainQuery, InterchainQueryChecker, - resolveOrDeployAccountOwner, } from '@hyperlane-xyz/sdk'; import { Contexts } from '../config/contexts.js'; @@ -53,11 +52,7 @@ async function check() { [fork]: { blocks: { confirmations: 0 } }, }); - const owner = await resolveOrDeployAccountOwner( - multiProvider, - fork, - envConfig.core[fork].owner, - ); + const owner = envConfig.core[fork].owner; const signer = await impersonateAccount(owner, 1e18); multiProvider.setSigner(fork, signer); diff --git a/typescript/infra/scripts/deploy.ts b/typescript/infra/scripts/deploy.ts index 733bfd819..73ce91679 100644 --- a/typescript/infra/scripts/deploy.ts +++ b/typescript/infra/scripts/deploy.ts @@ -7,7 +7,6 @@ import { ChainMap, ContractVerifier, ExplorerLicenseType, - FallbackRoutingHookConfig, HypERC20Deployer, HyperlaneCoreDeployer, HyperlaneDeployer, @@ -25,7 +24,6 @@ import { objMap } from '@hyperlane-xyz/utils'; import { Contexts } from '../config/contexts.js'; import { core as coreConfig } from '../config/environments/mainnet3/core.js'; -import { DEPLOYER } from '../config/environments/mainnet3/owners.js'; import { getEnvAddresses } from '../config/registry.js'; import { getWarpConfig } from '../config/warp.js'; import { deployWithArtifacts } from '../src/deployment/deploy.js'; @@ -198,11 +196,7 @@ async function main() { ); // Config is intended to be changed for ad-hoc use cases: config = { - ethereum: { - ...(coreConfig.ethereum.defaultHook as FallbackRoutingHookConfig) - .domains.ancient8, - owner: DEPLOYER, - }, + ethereum: coreConfig.ethereum.defaultHook, }; } else { console.log(`Skipping ${module}, deployer unimplemented`); diff --git a/typescript/infra/scripts/helloworld/kathy.ts b/typescript/infra/scripts/helloworld/kathy.ts index 101bd6779..7c212831e 100644 --- a/typescript/infra/scripts/helloworld/kathy.ts +++ b/typescript/infra/scripts/helloworld/kathy.ts @@ -13,7 +13,6 @@ import { ProviderType, RpcConsensusType, TypedTransactionReceipt, - resolveOrDeployAccountOwner, } from '@hyperlane-xyz/sdk'; import { Address, @@ -248,12 +247,11 @@ async function main(): Promise { } chains.map(async (chain) => { - const owner = await resolveOrDeployAccountOwner( - multiProvider, + return updateWalletBalanceMetricFor( + app, chain, coreConfig.owners[chain].owner, ); - return updateWalletBalanceMetricFor(app, chain, owner); }); // Incremented each time an entire cycle has occurred @@ -372,11 +370,7 @@ async function main(): Promise { messagesSendCount.labels({ ...labels, status: 'failure' }).inc(); errorOccurred = true; } - const owner = await resolveOrDeployAccountOwner( - multiProvider, - origin, - coreConfig.owners[origin].owner, - ); + const owner = coreConfig.owners[origin].owner; updateWalletBalanceMetricFor(app, origin, owner).catch((e) => { logger.warn('Failed to update wallet balance for chain', { chain: origin, diff --git a/typescript/infra/test/govern.hardhat-test.ts b/typescript/infra/test/govern.hardhat-test.ts index e5852d1f3..876e8ddd0 100644 --- a/typescript/infra/test/govern.hardhat-test.ts +++ b/typescript/infra/test/govern.hardhat-test.ts @@ -26,7 +26,6 @@ import { TestChainName, TestCoreApp, TestCoreDeployer, - resolveOrDeployAccountOwner, } from '@hyperlane-xyz/sdk'; import { Address, CallData, eqAddress } from '@hyperlane-xyz/utils'; @@ -134,11 +133,7 @@ describe('ICA governance', async () => { localRouter: remote.address, }; - accountOwner = await resolveOrDeployAccountOwner( - multiProvider, - remoteChain, - accountConfig, - ); + accountOwner = await icaApp.deployAccount(remoteChain, accountConfig); const recipientF = new TestRecipient__factory(signer); recipient = await recipientF.deploy(); diff --git a/typescript/sdk/src/core/CoreDeployer.hardhat-test.ts b/typescript/sdk/src/core/CoreDeployer.hardhat-test.ts index 58bf38f27..d4697599c 100644 --- a/typescript/sdk/src/core/CoreDeployer.hardhat-test.ts +++ b/typescript/sdk/src/core/CoreDeployer.hardhat-test.ts @@ -8,7 +8,7 @@ import { objMap, promiseObjAll } from '@hyperlane-xyz/utils'; import { TestChainName, testChains } from '../consts/testChains.js'; import { HyperlaneContractsMap } from '../contracts/types.js'; import { HyperlaneProxyFactoryDeployer } from '../deploy/HyperlaneProxyFactoryDeployer.js'; -import { HookConfig } from '../hook/types.js'; +import { DerivedHookConfig } from '../hook/EvmHookReader.js'; import { DerivedIsmConfig } from '../ism/EvmIsmReader.js'; import { HyperlaneIsmFactory } from '../ism/HyperlaneIsmFactory.js'; import { AggregationIsmConfig, IsmType } from '../ism/types.js'; @@ -140,12 +140,12 @@ describe('core', async () => { ); // Cast because we don't expect the 'string' type - const defaultIsmOnchain = + const { address: _, ...defaultIsmOnchain } = coreConfigOnChain.defaultIsm as DerivedIsmConfig; const defaultIsmTest = coreConfig[chainName] .defaultIsm as DerivedIsmConfig; - expect(defaultIsmOnchain.type).to.be.equal(defaultIsmTest.type); + expect(defaultIsmOnchain).to.deep.equal(defaultIsmTest); }), ); }); @@ -158,12 +158,12 @@ describe('core', async () => { ); // Cast because we don't expect the 'string' type - const defaultHookOnchain = - coreConfigOnChain.defaultHook as HookConfig; + const { address: _, ...defaultHookOnchain } = + coreConfigOnChain.defaultHook as DerivedHookConfig; const defaultHookTest = coreConfig[chainName] - .defaultHook as HookConfig; + .defaultHook as DerivedHookConfig; - expect(defaultHookOnchain.type).to.be.equal(defaultHookTest.type); + expect(defaultHookOnchain).to.deep.equal(defaultHookTest); }), ); }); @@ -174,13 +174,11 @@ describe('core', async () => { chainName, contract.mailbox.address, ); - const requiredHookOnchain = coreConfigOnChain.requiredHook; + const { address: _, ...requiredHookOnchain } = + coreConfigOnChain.requiredHook as DerivedHookConfig; const requiredHookTest = coreConfig[chainName].requiredHook; - // Test all the fields - objMap(requiredHookTest, (key, value) => { - expect(requiredHookOnchain[key]).to.be.equal(value); - }); + expect(requiredHookOnchain).to.deep.equal(requiredHookTest); }), ); }); diff --git a/typescript/sdk/src/core/HyperlaneCoreDeployer.ts b/typescript/sdk/src/core/HyperlaneCoreDeployer.ts index 6a06fa88f..380bcff71 100644 --- a/typescript/sdk/src/core/HyperlaneCoreDeployer.ts +++ b/typescript/sdk/src/core/HyperlaneCoreDeployer.ts @@ -1,5 +1,6 @@ import { IPostDispatchHook, + IPostDispatchHook__factory, Mailbox, TestRecipient, ValidatorAnnounce, @@ -139,7 +140,7 @@ export class HyperlaneCoreDeployer extends HyperlaneDeployer< await this.configureHook( chain, mailbox, - defaultHook, + defaultHook.address, (_mailbox) => _mailbox.defaultHook(), (_mailbox, _hook) => _mailbox.populateTransaction.setDefaultHook(_hook, { ...overrides }), @@ -148,7 +149,7 @@ export class HyperlaneCoreDeployer extends HyperlaneDeployer< await this.configureHook( chain, mailbox, - requiredHook, + requiredHook.address, (_mailbox) => _mailbox.requiredHook(), (_mailbox, _hook) => _mailbox.populateTransaction.setRequiredHook(_hook, { ...overrides }), @@ -184,6 +185,13 @@ export class HyperlaneCoreDeployer extends HyperlaneDeployer< config: HookConfig, coreAddresses: Partial, ): Promise { + if (typeof config === 'string') { + return IPostDispatchHook__factory.connect( + config, + this.multiProvider.getProvider(chain), + ); + } + const hooks = await this.hookDeployer.deployContracts( chain, config, diff --git a/typescript/sdk/src/core/schemas.ts b/typescript/sdk/src/core/schemas.ts new file mode 100644 index 000000000..22c422326 --- /dev/null +++ b/typescript/sdk/src/core/schemas.ts @@ -0,0 +1,9 @@ +import { HookConfigSchema } from '../hook/schemas.js'; +import { IsmConfigSchema } from '../ism/schemas.js'; +import { OwnableSchema } from '../schemas.js'; + +export const CoreConfigSchema = OwnableSchema.extend({ + defaultIsm: IsmConfigSchema, + defaultHook: HookConfigSchema, + requiredHook: HookConfigSchema, +}); diff --git a/typescript/sdk/src/core/types.ts b/typescript/sdk/src/core/types.ts index fa86a50a6..b21290648 100644 --- a/typescript/sdk/src/core/types.ts +++ b/typescript/sdk/src/core/types.ts @@ -1,18 +1,16 @@ +import { z } from 'zod'; + import type { Mailbox } from '@hyperlane-xyz/core'; import type { Address, ParsedMessage } from '@hyperlane-xyz/utils'; import type { UpgradeConfig } from '../deploy/proxy.js'; -import type { CheckerViolation, OwnableConfig } from '../deploy/types.js'; -import { HookConfig } from '../hook/types.js'; +import type { CheckerViolation } from '../deploy/types.js'; import type { IsmConfig } from '../ism/types.js'; import type { ChainName } from '../types.js'; -import { CoreFactories } from './contracts.js'; +import { CoreConfigSchema } from './schemas.js'; -export type CoreConfig = OwnableConfig & { - defaultIsm: IsmConfig; - defaultHook: HookConfig; - requiredHook: HookConfig; +export type CoreConfig = z.infer & { remove?: boolean; upgrade?: UpgradeConfig; }; diff --git a/typescript/sdk/src/deploy/HyperlaneAppChecker.ts b/typescript/sdk/src/deploy/HyperlaneAppChecker.ts index fdebf57ab..41419e088 100644 --- a/typescript/sdk/src/deploy/HyperlaneAppChecker.ts +++ b/typescript/sdk/src/deploy/HyperlaneAppChecker.ts @@ -21,12 +21,10 @@ import { AccessControlViolation, BytecodeMismatchViolation, CheckerViolation, - Owner, OwnerViolation, ProxyAdminViolation, TimelockControllerViolation, ViolationType, - resolveOrDeployAccountOwner, } from './types.js'; export abstract class HyperlaneAppChecker< @@ -208,19 +206,12 @@ export abstract class HyperlaneAppChecker< protected async checkOwnership( chain: ChainName, - owner: Owner, + owner: Address, ownableOverrides?: Record, ): Promise { const ownableContracts = await this.ownables(chain); for (const [name, contract] of Object.entries(ownableContracts)) { - let expectedOwner = ownableOverrides?.[name] ?? owner; - if (typeof expectedOwner !== 'string') { - expectedOwner = await resolveOrDeployAccountOwner( - this.multiProvider, - chain, - expectedOwner, - ); - } + const expectedOwner = ownableOverrides?.[name] ?? owner; const actual = await contract.owner(); if (!eqAddress(actual, expectedOwner)) { const violation: OwnerViolation = { diff --git a/typescript/sdk/src/deploy/HyperlaneDeployer.ts b/typescript/sdk/src/deploy/HyperlaneDeployer.ts index d9fefa0e1..91815b504 100644 --- a/typescript/sdk/src/deploy/HyperlaneDeployer.ts +++ b/typescript/sdk/src/deploy/HyperlaneDeployer.ts @@ -2,8 +2,6 @@ import { Contract, PopulatedTransaction, ethers } from 'ethers'; import { Logger } from 'pino'; import { - IPostDispatchHook, - IPostDispatchHook__factory, ITransparentUpgradeableProxy, MailboxClient, Ownable, @@ -28,6 +26,7 @@ import { HyperlaneContractsMap, HyperlaneFactories, } from '../contracts/types.js'; +import { HookConfig } from '../hook/types.js'; import { HyperlaneIsmFactory } from '../ism/HyperlaneIsmFactory.js'; import { IsmConfig } from '../ism/types.js'; import { moduleMatchesConfig } from '../ism/utils.js'; @@ -64,7 +63,7 @@ export interface DeployerOptions { } export abstract class HyperlaneDeployer< - Config extends object, + Config, Factories extends HyperlaneFactories, > { public verificationInputs: ChainMap = {}; @@ -291,32 +290,31 @@ export abstract class HyperlaneDeployer< protected async configureHook( chain: ChainName, contract: C, - targetHook: IPostDispatchHook, + config: HookConfig, getHook: (contract: C) => Promise
, setHook: (contract: C, hook: Address) => Promise, ): Promise { + if (typeof config !== 'string') { + throw new Error('Legacy deployer does not support hook objects'); + } + const configuredHook = await getHook(contract); - if (!eqAddress(targetHook.address, configuredHook)) { - const result = await this.runIfOwner(chain, contract, async () => { + if (!eqAddress(config, configuredHook)) { + await this.runIfOwner(chain, contract, async () => { this.logger.debug( - `Set hook on ${chain} to ${targetHook.address}, currently is ${configuredHook}`, + `Set hook on ${chain} to ${config}, currently is ${configuredHook}`, ); await this.multiProvider.sendTransaction( chain, - setHook(contract, targetHook.address), + setHook(contract, config), ); const actualHook = await getHook(contract); - if (!eqAddress(targetHook.address, actualHook)) { + if (!eqAddress(config, actualHook)) { throw new Error( - `Set hook failed on ${chain}, wanted ${targetHook.address}, got ${actualHook}`, + `Set hook failed on ${chain}, wanted ${config}, 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 }); - } } } @@ -332,10 +330,7 @@ export abstract class HyperlaneDeployer< await this.configureHook( local, client, - IPostDispatchHook__factory.connect( - config.hook, - this.multiProvider.getSignerOrProvider(local), - ), + config.hook, (_client) => _client.hook(), (_client, _hook) => _client.populateTransaction.setHook(_hook), ); @@ -719,10 +714,10 @@ export abstract class HyperlaneDeployer< return ret; } - async transferOwnershipOfContracts( + async transferOwnershipOfContracts( chain: ChainName, - config: OwnableConfig, - ownables: Partial>, + config: OwnableConfig, + ownables: Partial>, ): Promise { const receipts: ethers.ContractReceipt[] = []; for (const [contractName, ownable] of Object.entries( @@ -732,7 +727,7 @@ export abstract class HyperlaneDeployer< continue; } const current = await ownable.owner(); - const owner = config.ownerOverrides?.[contractName as K] ?? config.owner; + const owner = config.ownerOverrides?.[contractName] ?? config.owner; if (!eqAddress(current, owner)) { this.logger.debug( { contractName, current, desiredOwner: owner }, diff --git a/typescript/sdk/src/deploy/types.ts b/typescript/sdk/src/deploy/types.ts index 5c74845a4..ad78b97b1 100644 --- a/typescript/sdk/src/deploy/types.ts +++ b/typescript/sdk/src/deploy/types.ts @@ -8,44 +8,10 @@ import type { } from '@hyperlane-xyz/core'; import { Address } from '@hyperlane-xyz/utils'; -import { deployInterchainAccount } from '../middleware/account/InterchainAccount.js'; -import { AccountConfig } from '../middleware/account/types.js'; -import { MultiProvider } from '../providers/MultiProvider.js'; +import { OwnableSchema } from '../schemas.js'; import type { ChainName } from '../types.js'; -import { OwnableConfigSchema } from './schemas.js'; - -export type Owner = Address | AccountConfig; - -/** - * @remarks ownerOverrides is added outside of the Schema because zod handle generics in a weird way (uses functions) - * @see https://stackoverflow.com/questions/74907523/creating-zod-schema-for-generic-interface - */ -export type OwnableConfig = z.infer< - typeof OwnableConfigSchema -> & { - ownerOverrides?: Partial>; -}; - -export async function resolveOrDeployAccountOwner( - multiProvider: MultiProvider, - chain: ChainName, - owner: Owner, -): Promise
{ - if (typeof owner === 'string') { - return owner; - } else { - if (!owner.localRouter) { - throw new Error('localRouter is required for AccountConfig'); - } - // submits a transaction to deploy an interchain account if the owner is an AccountConfig and the ICA isn't not deployed yet - return deployInterchainAccount(multiProvider, chain, owner); - } -} - -export function isOwnableConfig(config: object): config is OwnableConfig { - return 'owner' in config; -} +export type OwnableConfig = z.infer; export interface CheckerViolation { chain: ChainName; diff --git a/typescript/sdk/src/gas/HyperlaneIgpDeployer.ts b/typescript/sdk/src/gas/HyperlaneIgpDeployer.ts index dc6efc37c..fcaba58da 100644 --- a/typescript/sdk/src/gas/HyperlaneIgpDeployer.ts +++ b/typescript/sdk/src/gas/HyperlaneIgpDeployer.ts @@ -15,7 +15,10 @@ import { MultiProvider } from '../providers/MultiProvider.js'; import { ChainName } from '../types.js'; import { IgpFactories, igpFactories } from './contracts.js'; -import { serializeDifference } from './oracle/types.js'; +import { + oracleConfigToOracleData, + serializeDifference, +} from './oracle/types.js'; import { IgpConfig } from './types.js'; export class HyperlaneIgpDeployer extends HyperlaneDeployer< @@ -120,22 +123,24 @@ export class HyperlaneIgpDeployer extends HyperlaneDeployer< const actual = await gasOracle.remoteGasData(remoteDomain); + const desiredData = oracleConfigToOracleData(desired); + if ( !actual.gasPrice.eq(desired.gasPrice) || !actual.tokenExchangeRate.eq(desired.tokenExchangeRate) ) { this.logger.info( - `${chain} -> ${remote}: ${serializeDifference(actual, desired)}`, + `${chain} -> ${remote}: ${serializeDifference(actual, desiredData)}`, ); configsToSet.push({ remoteDomain, - ...desired, + ...desiredData, }); } const exampleRemoteGas = (config.overhead[remote] ?? 200_000) + 50_000; - const exampleRemoteGasCost = desired.tokenExchangeRate - .mul(desired.gasPrice) + const exampleRemoteGasCost = desiredData.tokenExchangeRate + .mul(desiredData.gasPrice) .mul(exampleRemoteGas) .div(TOKEN_EXCHANGE_RATE_SCALE); this.logger.info( 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 index 129ea852d..8eb66a6ed 100644 --- a/typescript/sdk/src/gas/oracle/configure-gas-oracles.hardhat-test.ts +++ b/typescript/sdk/src/gas/oracle/configure-gas-oracles.hardhat-test.ts @@ -11,6 +11,8 @@ import { ChainMap } from '../../types.js'; import { HyperlaneIgpDeployer } from '../HyperlaneIgpDeployer.js'; import { IgpConfig } from '../types.js'; +import { oracleConfigToOracleData } from './types.js'; + describe('HyperlaneIgpDeployer', () => { const local = TestChainName.test1; const remote = TestChainName.test2; @@ -36,13 +38,15 @@ describe('HyperlaneIgpDeployer', () => { expect({ gasPrice: deployedConfig.gasPrice, tokenExchangeRate: deployedConfig.tokenExchangeRate, - }).to.deep.equal(testConfig[local].oracleConfig![remote]); + }).to.deep.equal( + oracleConfigToOracleData(testConfig[local].oracleConfig![remote]), + ); }); it('should configure new oracle config', async () => { testConfig[local].oracleConfig![remote] = { - tokenExchangeRate: utils.parseUnits('2', 'gwei'), - gasPrice: utils.parseUnits('3', 'gwei'), + tokenExchangeRate: utils.parseUnits('2', 'gwei').toString(), + gasPrice: utils.parseUnits('3', 'gwei').toString(), }; const localContracts = await deployer.deployContracts( @@ -55,6 +59,8 @@ describe('HyperlaneIgpDeployer', () => { expect({ gasPrice: modifiedConfig.gasPrice, tokenExchangeRate: modifiedConfig.tokenExchangeRate, - }).to.deep.equal(testConfig[local].oracleConfig![remote]); + }).to.deep.equal( + oracleConfigToOracleData(testConfig[local].oracleConfig![remote]), + ); }); }); diff --git a/typescript/sdk/src/gas/oracle/types.ts b/typescript/sdk/src/gas/oracle/types.ts index c4ba2fb45..99669b7ea 100644 --- a/typescript/sdk/src/gas/oracle/types.ts +++ b/typescript/sdk/src/gas/oracle/types.ts @@ -1,21 +1,25 @@ import { ethers } from 'ethers'; - -import { StorageGasOracle } from '@hyperlane-xyz/core'; +import { z } from 'zod'; import { TOKEN_EXCHANGE_RATE_DECIMALS } from '../../consts/igp.js'; -export enum GasOracleContractType { - StorageGasOracle = 'StorageGasOracle', -} +export const StorageGasOracleConfigSchema = z.object({ + gasPrice: z.string(), + tokenExchangeRate: z.string(), +}); // Gas data to configure on a single destination chain. -export type StorageGasOracleConfig = Pick< - StorageGasOracle.RemoteGasDataConfigStructOutput, - 'gasPrice' | 'tokenExchangeRate' +export type StorageGasOracleConfig = z.output< + typeof StorageGasOracleConfigSchema >; +export type OracleData = { + tokenExchangeRate: ethers.BigNumber; + gasPrice: ethers.BigNumber; +}; + export const formatGasOracleConfig = ( - config: StorageGasOracleConfig, + config: OracleData, ): { tokenExchangeRate: string; gasPrice: string; @@ -43,9 +47,17 @@ const serializePercentDifference = ( return diff.isNegative() ? `${diff.toString()}%` : `+${diff.toString()}%`; }; +// TODO: replace once #3771 is fixed +export const oracleConfigToOracleData = ( + config: StorageGasOracleConfig, +): OracleData => ({ + gasPrice: ethers.BigNumber.from(config.gasPrice), + tokenExchangeRate: ethers.BigNumber.from(config.tokenExchangeRate), +}); + export const serializeDifference = ( - actual: StorageGasOracleConfig, - expected: StorageGasOracleConfig, + actual: OracleData, + expected: OracleData, ): string => { const gasPriceDiff = serializePercentDifference( actual.gasPrice, diff --git a/typescript/sdk/src/gas/types.ts b/typescript/sdk/src/gas/types.ts index dacebc2f7..55114478d 100644 --- a/typescript/sdk/src/gas/types.ts +++ b/typescript/sdk/src/gas/types.ts @@ -1,26 +1,14 @@ import { BigNumber } from 'ethers'; +import { z } from 'zod'; import { InterchainGasPaymaster } from '@hyperlane-xyz/core'; import type { Address } from '@hyperlane-xyz/utils'; -import type { CheckerViolation, OwnableConfig } from '../deploy/types.js'; +import type { CheckerViolation } from '../deploy/types.js'; +import { IgpSchema } from '../hook/schemas.js'; import { ChainMap } from '../types.js'; -import { IgpFactories } from './contracts.js'; -import { - GasOracleContractType, - StorageGasOracleConfig, -} from './oracle/types.js'; - -export type IgpConfig = OwnableConfig & { - beneficiary: Address; - oracleKey: Address; - overhead: ChainMap; - // TODO: require this - oracleConfig?: ChainMap; - // DEPRECATED - gasOracleType?: ChainMap; -}; +export type IgpConfig = z.infer; export enum IgpViolationType { Beneficiary = 'Beneficiary', diff --git a/typescript/sdk/src/hook/EvmHookReader.test.ts b/typescript/sdk/src/hook/EvmHookReader.test.ts index 77af11b99..e9d45e428 100644 --- a/typescript/sdk/src/hook/EvmHookReader.test.ts +++ b/typescript/sdk/src/hook/EvmHookReader.test.ts @@ -1,5 +1,6 @@ import { expect } from 'chai'; import { ethers } from 'ethers'; +import { randomBytes } from 'ethers/lib/utils.js'; import sinon from 'sinon'; import { @@ -117,11 +118,13 @@ describe('EvmHookReader', () => { it('should derive pausable config correctly', async () => { const mockAddress = generateRandomAddress(); const mockOwner = generateRandomAddress(); + const mockPaused = randomBytes(1)[0] % 2 === 0; // Mocking the connect method + returned what we need from contract object const mockContract = { hookType: sandbox.stub().resolves(OnchainHookType.PAUSABLE), owner: sandbox.stub().resolves(mockOwner), + paused: sandbox.stub().resolves(mockPaused), }; sandbox .stub(PausableHook__factory, 'connect') @@ -132,6 +135,7 @@ describe('EvmHookReader', () => { const expectedConfig: WithAddress = { owner: mockOwner, + paused: mockPaused, address: mockAddress, type: HookType.PAUSABLE, }; diff --git a/typescript/sdk/src/hook/EvmHookReader.ts b/typescript/sdk/src/hook/EvmHookReader.ts index c5db5165c..e4fa97198 100644 --- a/typescript/sdk/src/hook/EvmHookReader.ts +++ b/typescript/sdk/src/hook/EvmHookReader.ts @@ -42,7 +42,7 @@ import { RoutingHookConfig, } from './types.js'; -export type DerivedHookConfig = WithAddress; +export type DerivedHookConfig = WithAddress>; export interface HookReader { deriveHookConfig(address: Address): Promise>; @@ -177,7 +177,10 @@ export class EvmHookReader implements HookReader { const domainGasOverhead = await hook.destinationGasLimit(domainId, 0); overhead[chainName] = domainGasOverhead.toNumber(); - oracleConfig[chainName] = { tokenExchangeRate, gasPrice }; + oracleConfig[chainName] = { + tokenExchangeRate: tokenExchangeRate.toString(), + gasPrice: gasPrice.toString(), + }; const { gasOracle } = await hook.destinationGasConfigs(domainId); const oracle = StorageGasOracle__factory.connect( @@ -334,9 +337,11 @@ export class EvmHookReader implements HookReader { assert((await hook.hookType()) === OnchainHookType.PAUSABLE); const owner = await hook.owner(); + const paused = await hook.paused(); return { owner, address, + paused, type: HookType.PAUSABLE, }; } diff --git a/typescript/sdk/src/hook/HyperlaneHookDeployer.ts b/typescript/sdk/src/hook/HyperlaneHookDeployer.ts index d1877c930..7980d627a 100644 --- a/typescript/sdk/src/hook/HyperlaneHookDeployer.ts +++ b/typescript/sdk/src/hook/HyperlaneHookDeployer.ts @@ -64,6 +64,10 @@ export class HyperlaneHookDeployer extends HyperlaneDeployer< config: HookConfig, coreAddresses = this.core[chain], ): Promise> { + if (typeof config === 'string') { + throw new Error('Hook deployer should not receive address config'); + } + let hook: DeployedHook; if (config.type === HookType.MERKLE_TREE) { const mailbox = coreAddresses.mailbox; @@ -92,11 +96,9 @@ export class HyperlaneHookDeployer extends HyperlaneDeployer< 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 }, - ); + await this.transferOwnershipOfContracts(chain, config, { + [HookType.PAUSABLE]: hook, + }); } else { throw new Error(`Unsupported hook config: ${config}`); } @@ -151,6 +153,11 @@ export class HyperlaneHookDeployer extends HyperlaneDeployer< const aggregatedHooks: string[] = []; let hooks: any = {}; for (const hookConfig of config.hooks) { + if (typeof hookConfig === 'string') { + aggregatedHooks.push(hookConfig); + continue; + } + const subhooks = await this.deployContracts( chain, hookConfig, @@ -159,9 +166,7 @@ export class HyperlaneHookDeployer extends HyperlaneDeployer< aggregatedHooks.push(subhooks[hookConfig.type].address); hooks = { ...hooks, ...subhooks }; } - this.logger.debug( - `Deploying aggregation hook of ${config.hooks.map((h) => h.type)}`, - ); + this.logger.debug(`Deploying aggregation hook of ${config.hooks}`); const address = await this.ismFactory.deployStaticAddressSet( chain, this.ismFactory.getContracts(chain).staticAggregationHookFactory, @@ -275,15 +280,21 @@ export class HyperlaneHookDeployer extends HyperlaneDeployer< } case HookType.FALLBACK_ROUTING: { this.logger.debug('Deploying FallbackDomainRoutingHook for %s', chain); - const fallbackHook = await this.deployContracts( - chain, - config.fallback, - coreAddresses, - ); + let fallbackAddress: Address; + if (typeof config.fallback === 'string') { + fallbackAddress = config.fallback; + } else { + const fallbackHook = await this.deployContracts( + chain, + config.fallback, + coreAddresses, + ); + fallbackAddress = fallbackHook[config.fallback.type].address; + } routingHook = await this.deployContract( chain, HookType.FALLBACK_ROUTING, - [mailbox, deployer, fallbackHook[config.fallback.type].address], + [mailbox, deployer, fallbackAddress], ); break; } diff --git a/typescript/sdk/src/hook/schemas.ts b/typescript/sdk/src/hook/schemas.ts new file mode 100644 index 000000000..d09440b60 --- /dev/null +++ b/typescript/sdk/src/hook/schemas.ts @@ -0,0 +1,78 @@ +import { z } from 'zod'; + +import { StorageGasOracleConfigSchema } from '../gas/oracle/types.js'; +import { ZHash } from '../metadata/customZodTypes.js'; +import { OwnableSchema, PausableSchema } from '../schemas.js'; + +import { + AggregationHookConfig, + DomainRoutingHookConfig, + FallbackRoutingHookConfig, + HookType, +} from './types.js'; + +export const ProtocolFeeSchema = OwnableSchema.extend({ + type: z.literal(HookType.PROTOCOL_FEE), + beneficiary: z.string(), + maxProtocolFee: z.string(), + protocolFee: z.string(), +}); + +export const MerkleTreeSchema = z.object({ + type: z.literal(HookType.MERKLE_TREE), +}); + +export const PausableHookSchema = PausableSchema.extend({ + type: z.literal(HookType.PAUSABLE), +}); + +export const OpStackHookSchema = OwnableSchema.extend({ + type: z.literal(HookType.OP_STACK), + nativeBridge: z.string(), + destinationChain: z.string(), +}); + +export const IgpSchema = OwnableSchema.extend({ + type: z.literal(HookType.INTERCHAIN_GAS_PAYMASTER), + beneficiary: z.string(), + oracleKey: z.string(), + overhead: z.record(z.number()), + oracleConfig: z.record(StorageGasOracleConfigSchema), +}); + +export const DomainRoutingHookConfigSchema: z.ZodSchema = + z.lazy(() => + OwnableSchema.extend({ + type: z.literal(HookType.ROUTING), + domains: z.record(HookConfigSchema), + }), + ); + +export const FallbackRoutingHookConfigSchema: z.ZodSchema = + z.lazy(() => + OwnableSchema.extend({ + type: z.literal(HookType.FALLBACK_ROUTING), + domains: z.record(HookConfigSchema), + fallback: HookConfigSchema, + }), + ); + +export const AggregationHookConfigSchema: z.ZodSchema = + z.lazy(() => + z.object({ + type: z.literal(HookType.AGGREGATION), + hooks: z.array(HookConfigSchema), + }), + ); + +export const HookConfigSchema = z.union([ + ZHash, + ProtocolFeeSchema, + PausableHookSchema, + OpStackHookSchema, + MerkleTreeSchema, + IgpSchema, + DomainRoutingHookConfigSchema, + FallbackRoutingHookConfigSchema, + AggregationHookConfigSchema, +]); diff --git a/typescript/sdk/src/hook/types.ts b/typescript/sdk/src/hook/types.ts index 3b5e8f114..8cbd26fd2 100644 --- a/typescript/sdk/src/hook/types.ts +++ b/typescript/sdk/src/hook/types.ts @@ -1,8 +1,16 @@ -import { Address } from '@hyperlane-xyz/utils'; +import { z } from 'zod'; import { OwnableConfig } from '../deploy/types.js'; -import { IgpConfig } from '../gas/types.js'; -import { ChainMap, ChainName } from '../types.js'; +import { ChainMap } from '../types.js'; + +import { + HookConfigSchema, + IgpSchema, + MerkleTreeSchema, + OpStackHookSchema, + PausableHookSchema, + ProtocolFeeSchema, +} from './schemas.js'; // As found in IPostDispatchHook.sol export enum OnchainHookType { @@ -29,60 +37,26 @@ export enum HookType { PAUSABLE = 'pausableHook', } -export type MerkleTreeHookConfig = { - type: HookType.MERKLE_TREE; -}; +export type MerkleTreeHookConfig = z.infer; +export type IgpHookConfig = z.infer; +export type ProtocolFeeHookConfig = z.infer; +export type PausableHookConfig = z.infer; +export type OpStackHookConfig = z.infer; +// explicitly typed to avoid zod circular dependency export type AggregationHookConfig = { type: HookType.AGGREGATION; hooks: Array; }; - -export type IgpHookConfig = IgpConfig & { - type: HookType.INTERCHAIN_GAS_PAYMASTER; -}; - -export type ProtocolFeeHookConfig = OwnableConfig & { - type: HookType.PROTOCOL_FEE; - maxProtocolFee: string; - protocolFee: string; - beneficiary: Address; -}; - -export type PausableHookConfig = OwnableConfig & { - type: HookType.PAUSABLE; -}; - -export type OpStackHookConfig = OwnableConfig & { - type: HookType.OP_STACK; - nativeBridge: Address; - destinationChain: ChainName; -}; - export type RoutingHookConfig = OwnableConfig & { domains: ChainMap; }; - export type DomainRoutingHookConfig = RoutingHookConfig & { type: HookType.ROUTING; }; - export type FallbackRoutingHookConfig = RoutingHookConfig & { type: HookType.FALLBACK_ROUTING; fallback: HookConfig; }; -export type HookConfig = - | MerkleTreeHookConfig - | AggregationHookConfig - | IgpHookConfig - | ProtocolFeeHookConfig - | OpStackHookConfig - | DomainRoutingHookConfig - | FallbackRoutingHookConfig - | PausableHookConfig; - -export type HooksConfig = { - required: HookConfig; - default: HookConfig; -}; +export type HookConfig = z.infer; diff --git a/typescript/sdk/src/index.ts b/typescript/sdk/src/index.ts index 79f780df7..3c45dab6b 100644 --- a/typescript/sdk/src/index.ts +++ b/typescript/sdk/src/index.ts @@ -75,6 +75,7 @@ export { MailboxViolationType, ValidatorAnnounceViolation, } from './core/types.js'; +export { CoreConfigSchema } from './core/schemas.js'; export { HyperlaneAppChecker } from './deploy/HyperlaneAppChecker.js'; export { DeployerOptions, @@ -86,7 +87,6 @@ export { OwnableConfig, OwnerViolation, ViolationType, - resolveOrDeployAccountOwner, } from './deploy/types.js'; export { ContractVerifier } from './deploy/verify/ContractVerifier.js'; export { PostDeploymentContractVerifier } from './deploy/verify/PostDeploymentContractVerifier.js'; @@ -110,10 +110,7 @@ export { SealevelOverheadIgpDataSchema, } from './gas/adapters/serialization.js'; export { IgpFactories, igpFactories } from './gas/contracts.js'; -export { - GasOracleContractType, - StorageGasOracleConfig, -} from './gas/oracle/types.js'; +export { StorageGasOracleConfig } from './gas/oracle/types.js'; export { CoinGeckoTokenPriceGetter } from './gas/token-prices.js'; export { IgpBeneficiaryViolation, @@ -131,13 +128,13 @@ export { FallbackRoutingHookConfig, HookConfig, HookType, - HooksConfig, IgpHookConfig, MerkleTreeHookConfig, OpStackHookConfig, PausableHookConfig, ProtocolFeeHookConfig, } from './hook/types.js'; +export { HookConfigSchema } from './hook/schemas.js'; export { HyperlaneIsmFactory } from './ism/HyperlaneIsmFactory.js'; export { buildAggregationIsmConfigs, @@ -155,6 +152,7 @@ export { OpStackIsmConfig, PausableIsmConfig, RoutingIsmConfig, + TrustedRelayerIsmConfig, } from './ism/types.js'; export { collectValidators, moduleCanCertainlyVerify } from './ism/utils.js'; export { @@ -454,7 +452,7 @@ export { WarpTypedTransaction, } from './warp/types.js'; -export { AggregationIsmConfigSchema } from './ism/schemas.js'; +export { AggregationIsmConfigSchema, IsmConfigSchema } from './ism/schemas.js'; export { MailboxClientConfigSchema as mailboxClientConfigSchema } from './router/schemas.js'; export { WarpRouteDeployConfigSchema, diff --git a/typescript/sdk/src/ism/EvmIsmCreator.ts b/typescript/sdk/src/ism/EvmIsmCreator.ts index 2b95e3c3d..f4f96736b 100644 --- a/typescript/sdk/src/ism/EvmIsmCreator.ts +++ b/typescript/sdk/src/ism/EvmIsmCreator.ts @@ -31,7 +31,6 @@ import { import { HyperlaneContracts } from '../contracts/types.js'; import { HyperlaneDeployer } from '../deploy/HyperlaneDeployer.js'; import { ProxyFactoryFactories } from '../deploy/contracts.js'; -import { resolveOrDeployAccountOwner } from '../deploy/types.js'; import { MultiProvider } from '../providers/MultiProvider.js'; import { ChainMap, ChainName } from '../types.js'; @@ -171,17 +170,8 @@ export class EvmIsmCreator { destination, new PausableIsm__factory(), IsmType.PAUSABLE, - [ - await resolveOrDeployAccountOwner( - this.multiProvider, - destination, - config.owner, - ), - ], + [config.owner], ); - await this.deployer.transferOwnershipOfContracts(destination, config, { - [IsmType.PAUSABLE]: contract, - }); break; case IsmType.TRUSTED_RELAYER: assert( @@ -331,11 +321,7 @@ export class EvmIsmCreator { } } else { const isms: ChainMap
= {}; - const owner = await resolveOrDeployAccountOwner( - this.multiProvider, - destination, - config.owner, - ); + const owner = config.owner; for (const origin of Object.keys(config.domains)) { const ism = await this.deploy({ @@ -416,11 +402,7 @@ export class EvmIsmCreator { ); const isms: ChainMap
= {}; - const owner = await resolveOrDeployAccountOwner( - this.multiProvider, - destination, - config.owner, - ); + const owner = config.owner; for (const origin of Object.keys(config.domains)) { const ism = await this.deploy({ diff --git a/typescript/sdk/src/ism/HyperlaneIsmFactory.ts b/typescript/sdk/src/ism/HyperlaneIsmFactory.ts index 1f679ca62..98161d73e 100644 --- a/typescript/sdk/src/ism/HyperlaneIsmFactory.ts +++ b/typescript/sdk/src/ism/HyperlaneIsmFactory.ts @@ -36,7 +36,6 @@ import { ProxyFactoryFactories, proxyFactoryFactories, } from '../deploy/contracts.js'; -import { resolveOrDeployAccountOwner } from '../deploy/types.js'; import { MultiProvider } from '../providers/MultiProvider.js'; import { ChainMap, ChainName } from '../types.js'; @@ -151,13 +150,7 @@ export class HyperlaneIsmFactory extends HyperlaneApp { destination, new PausableIsm__factory(), IsmType.PAUSABLE, - [ - await resolveOrDeployAccountOwner( - this.multiProvider, - destination, - config.owner, - ), - ], + [config.owner], ); await this.deployer.transferOwnershipOfContracts(destination, config, { [IsmType.PAUSABLE]: contract, @@ -327,11 +320,6 @@ export class HyperlaneIsmFactory extends HyperlaneApp { } } else { const isms: ChainMap
= {}; - const owner = await resolveOrDeployAccountOwner( - this.multiProvider, - destination, - config.owner, - ); for (const origin of Object.keys(config.domains)) { const ism = await this.deploy({ destination, @@ -360,7 +348,7 @@ export class HyperlaneIsmFactory extends HyperlaneApp { receipt = await this.multiProvider.handleTx( destination, routingIsm['initialize(address,uint32[],address[])']( - owner, + config.owner, safeConfigDomains, submoduleAddresses, overrides, @@ -368,13 +356,8 @@ export class HyperlaneIsmFactory extends HyperlaneApp { ); } else { // deploying new domain routing ISM - const owner = await resolveOrDeployAccountOwner( - this.multiProvider, - destination, - config.owner, - ); const tx = await domainRoutingIsmFactory.deploy( - owner, + config.owner, safeConfigDomains, submoduleAddresses, overrides, diff --git a/typescript/sdk/src/ism/metadata/builder.ts b/typescript/sdk/src/ism/metadata/builder.ts index 66e364bbc..a18d14dfb 100644 --- a/typescript/sdk/src/ism/metadata/builder.ts +++ b/typescript/sdk/src/ism/metadata/builder.ts @@ -75,6 +75,9 @@ export class BaseMetadataBuilder implements MetadataBuilder { case IsmType.MERKLE_ROOT_MULTISIG: case IsmType.MESSAGE_ID_MULTISIG: + if (typeof hook === 'string') { + throw new Error('Hook context must be an object (for multisig ISM)'); + } const merkleTreeHook = deepFind( hook, (v): v is WithAddress => diff --git a/typescript/sdk/src/ism/schemas.ts b/typescript/sdk/src/ism/schemas.ts index c449e0e42..176a5539c 100644 --- a/typescript/sdk/src/ism/schemas.ts +++ b/typescript/sdk/src/ism/schemas.ts @@ -1,9 +1,9 @@ import { z } from 'zod'; -import { OwnableConfigSchema } from '../deploy/schemas.js'; import { ZHash } from '../metadata/customZodTypes.js'; +import { OwnableSchema, PausableSchema } from '../schemas.js'; -import { AggregationIsmConfig, IsmConfig, IsmType } from './types.js'; +import { AggregationIsmConfig, IsmType, RoutingIsmConfig } from './types.js'; export const TestIsmConfigSchema = z.object({ type: z.literal(IsmType.TEST_ISM), @@ -25,10 +25,9 @@ export const OpStackIsmConfigSchema = z.object({ nativeBridge: z.string(), }); -export const PausableIsmConfigSchema = OwnableConfigSchema.and( +export const PausableIsmConfigSchema = PausableSchema.and( z.object({ type: z.literal(IsmType.PAUSABLE), - paused: z.boolean().optional(), }), ); @@ -41,40 +40,36 @@ export const MultisigIsmConfigSchema = MultisigConfigSchema.and( }), ); -export const RoutingIsmConfigSchema = OwnableConfigSchema.and( - z.object({ - type: z.union([ - z.literal(IsmType.ROUTING), - z.literal(IsmType.FALLBACK_ROUTING), - ]), - domains: z.record(z.string(), z.nativeEnum(IsmType)), - }), +export const RoutingIsmConfigSchema: z.ZodSchema = z.lazy( + () => + OwnableSchema.extend({ + type: z.union([ + z.literal(IsmType.ROUTING), + z.literal(IsmType.FALLBACK_ROUTING), + ]), + domains: z.record(IsmConfigSchema), + }), ); -export const AggregationIsmConfigSchema: z.ZodSchema = - z.lazy(() => - z - .object({ - type: z.literal(IsmType.AGGREGATION), - modules: z.array(IsmConfigSchema), - threshold: z.number(), - }) - .refine((data) => { - if (data.threshold > data.modules.length) return false; - - return true; - }), - ); +export const AggregationIsmConfigSchema: z.ZodSchema = z + .lazy(() => + z.object({ + type: z.literal(IsmType.AGGREGATION), + modules: z.array(IsmConfigSchema), + threshold: z.number(), + }), + ) + .refine((data) => data.threshold <= data.modules.length, { + message: 'Threshold must be less than or equal to the number of modules', + }); -export const IsmConfigSchema: z.ZodSchema = z.lazy(() => - z.union([ - z.string(), - TestIsmConfigSchema, - OpStackIsmConfigSchema, - PausableIsmConfigSchema, - TrustedRelayerIsmConfigSchema, - MultisigIsmConfigSchema, - RoutingIsmConfigSchema, - AggregationIsmConfigSchema, - ]), -); +export const IsmConfigSchema = z.union([ + ZHash, + TestIsmConfigSchema, + OpStackIsmConfigSchema, + PausableIsmConfigSchema, + TrustedRelayerIsmConfigSchema, + MultisigIsmConfigSchema, + RoutingIsmConfigSchema, + AggregationIsmConfigSchema, +]); diff --git a/typescript/sdk/src/ism/types.ts b/typescript/sdk/src/ism/types.ts index 89892662d..c1c27781c 100644 --- a/typescript/sdk/src/ism/types.ts +++ b/typescript/sdk/src/ism/types.ts @@ -1,3 +1,5 @@ +import { z } from 'zod'; + import { IAggregationIsm, IMultisigIsm, @@ -12,6 +14,15 @@ import type { Address, Domain, ValueOf } from '@hyperlane-xyz/utils'; import { OwnableConfig } from '../deploy/types.js'; import { ChainMap } from '../types.js'; +import { + IsmConfigSchema, + MultisigIsmConfigSchema, + OpStackIsmConfigSchema, + PausableIsmConfigSchema, + TestIsmConfigSchema, + TrustedRelayerIsmConfigSchema, +} from './schemas.js'; + // this enum should match the IInterchainSecurityModule.sol enum // meant for the relayer export enum ModuleType { @@ -65,18 +76,19 @@ export type MultisigConfig = { threshold: number; }; -export type MultisigIsmConfig = MultisigConfig & { - type: IsmType.MERKLE_ROOT_MULTISIG | IsmType.MESSAGE_ID_MULTISIG; -}; - -export type TestIsmConfig = { - type: IsmType.TEST_ISM; -}; +export type MultisigIsmConfig = z.infer; +export type TestIsmConfig = z.infer; +export type PausableIsmConfig = z.infer; +export type OpStackIsmConfig = z.infer; +export type TrustedRelayerIsmConfig = z.infer< + typeof TrustedRelayerIsmConfigSchema +>; -export type PausableIsmConfig = OwnableConfig & { - type: IsmType.PAUSABLE; - paused?: boolean; -}; +export type NullIsmConfig = + | TestIsmConfig + | PausableIsmConfig + | OpStackIsmConfig + | TrustedRelayerIsmConfig; export type RoutingIsmConfig = OwnableConfig & { type: IsmType.ROUTING | IsmType.FALLBACK_ROUTING; @@ -89,29 +101,7 @@ export type AggregationIsmConfig = { threshold: number; }; -export type OpStackIsmConfig = { - type: IsmType.OP_STACK; - origin: Address; - nativeBridge: Address; -}; - -export type TrustedRelayerIsmConfig = { - type: IsmType.TRUSTED_RELAYER; - relayer: Address; -}; - -export type NullIsmConfig = - | PausableIsmConfig - | TestIsmConfig - | OpStackIsmConfig - | TrustedRelayerIsmConfig; - -export type IsmConfig = - | Address - | NullIsmConfig - | RoutingIsmConfig - | MultisigIsmConfig - | AggregationIsmConfig; +export type IsmConfig = z.infer; export type DeployedIsmType = { [IsmType.ROUTING]: IRoutingIsm; diff --git a/typescript/sdk/src/ism/utils.ts b/typescript/sdk/src/ism/utils.ts index 2ee8a0d06..8f9cac53a 100644 --- a/typescript/sdk/src/ism/utils.ts +++ b/typescript/sdk/src/ism/utils.ts @@ -23,7 +23,6 @@ import { import { HyperlaneContracts } from '../contracts/types.js'; import { ProxyFactoryFactories } from '../deploy/contracts.js'; -import { resolveOrDeployAccountOwner } from '../deploy/types.js'; import { MultiProvider } from '../providers/MultiProvider.js'; import { ChainName } from '../types.js'; @@ -222,11 +221,7 @@ export async function moduleMatchesConfig( ); // Check that the RoutingISM owner matches the config const owner = await routingIsm.owner(); - const expectedOwner = await resolveOrDeployAccountOwner( - multiProvider, - chain, - config.owner, - ); + const expectedOwner = config.owner; matches &&= eqAddress(owner, expectedOwner); // check if the mailbox matches the config for fallback routing if (config.type === IsmType.FALLBACK_ROUTING) { @@ -314,11 +309,7 @@ export async function moduleMatchesConfig( case IsmType.PAUSABLE: { const pausableIsm = PausableIsm__factory.connect(moduleAddress, provider); const owner = await pausableIsm.owner(); - const expectedOwner = await resolveOrDeployAccountOwner( - multiProvider, - chain, - config.owner, - ); + const expectedOwner = config.owner; matches &&= eqAddress(owner, expectedOwner); if (config.paused) { @@ -360,11 +351,7 @@ export async function routingModuleDelta( }; // if owners don't match, we need to transfer ownership - const expectedOwner = await resolveOrDeployAccountOwner( - multiProvider, - destination, - config.owner, - ); + const expectedOwner = config.owner; if (!eqAddress(owner, normalizeAddress(expectedOwner))) delta.owner = expectedOwner; if (config.type === IsmType.FALLBACK_ROUTING) { diff --git a/typescript/sdk/src/middleware/account/InterchainAccountDeployer.ts b/typescript/sdk/src/middleware/account/InterchainAccountDeployer.ts index 13f430488..c752861fe 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 { Router } from '@hyperlane-xyz/core'; +import { assert } from '@hyperlane-xyz/utils'; import { HyperlaneContracts } from '../../contracts/types.js'; import { ContractVerifier } from '../../deploy/verify/ContractVerifier.js'; @@ -49,9 +50,16 @@ export class InterchainAccountDeployer extends ProxiedRouterDeployer< async initializeArgs(chain: string, config: RouterConfig): Promise { const owner = await this.multiProvider.getSignerAddress(chain); + if (config.interchainSecurityModule) { + assert( + typeof config.interchainSecurityModule === 'string', + 'ISM objects not supported in ICA deployer', + ); + } + return [ config.hook ?? ethers.constants.AddressZero, - config.interchainSecurityModule! as string, // deployed in deployContracts + config.interchainSecurityModule!, owner, ]; } diff --git a/typescript/sdk/src/router/HyperlaneRouterChecker.ts b/typescript/sdk/src/router/HyperlaneRouterChecker.ts index 06d52f748..543f3fdcf 100644 --- a/typescript/sdk/src/router/HyperlaneRouterChecker.ts +++ b/typescript/sdk/src/router/HyperlaneRouterChecker.ts @@ -1,8 +1,4 @@ -import { ethers } from 'ethers'; -import { zeroAddress } from 'viem'; - -import { IMailbox__factory } from '@hyperlane-xyz/core'; -import { addressToBytes32, eqAddress } from '@hyperlane-xyz/utils'; +import { addressToBytes32, assert, eqAddress } from '@hyperlane-xyz/utils'; import { HyperlaneFactories } from '../contracts/types.js'; import { HyperlaneAppChecker } from '../deploy/HyperlaneAppChecker.js'; @@ -15,7 +11,6 @@ import { RouterApp } from './RouterApps.js'; import { ClientViolation, ClientViolationType, - MailboxClientConfig, RouterConfig, RouterViolation, RouterViolationType, @@ -38,103 +33,76 @@ export class HyperlaneRouterChecker< async checkChain(chain: ChainName): Promise { await this.checkMailboxClient(chain); await this.checkEnrolledRouters(chain); - await super.checkOwnership(chain, this.configMap[chain].owner); + await super.checkOwnership( + chain, + this.configMap[chain].owner, + this.configMap[chain].ownerOverrides, + ); } async checkMailboxClient(chain: ChainName): Promise { const router = this.app.router(this.app.getContracts(chain)); - const checkMailboxClientProperty = async ( - property: keyof MailboxClientConfig, - actual: string, - violationType: ClientViolationType, - ) => { - const value = this.configMap[chain][property]; + const config = this.configMap[chain]; - // If the value is an object, it's an ISM config - // and we should make sure it matches the actual ISM config - if (value && typeof value === 'object') { - if (!this.ismFactory) { - throw Error( - 'ISM factory not provided to HyperlaneRouterChecker, cannot check object-based ISM config', - ); - } - - const matches = await moduleMatchesConfig( - chain, - actual, - value, - this.multiProvider, - this.ismFactory!.chainMap[chain], - ); + const mailboxAddr = await router.mailbox(); + if (!eqAddress(mailboxAddr, config.mailbox)) { + this.addViolation({ + chain, + type: ClientViolationType.Mailbox, + contract: router, + actual: mailboxAddr, + expected: config.mailbox, + }); + } - if (!matches) { - const violation: ClientViolation = { - chain, - type: violationType, - contract: router, - actual, - expected: value, - description: `ISM config does not match deployed ISM`, - }; - this.addViolation(violation); - } - return; - } - const expected = - value && typeof value === 'string' - ? value - : ethers.constants.AddressZero; - if (!eqAddress(actual, expected)) { - const violation: ClientViolation = { + if (config.hook) { + assert( + typeof config.hook === 'string', + 'Hook objects not supported in router checker', + ); + const hook = await router.hook(); + if (!eqAddress(hook, config.hook as string)) { + this.addViolation({ chain, - type: violationType, + type: ClientViolationType.Hook, contract: router, - actual, - expected, - }; - this.addViolation(violation); + actual: hook, + expected: config.hook, + }); } - }; + } - const mailboxAddr = await router.mailbox(); - await checkMailboxClientProperty( - 'mailbox', - mailboxAddr, - ClientViolationType.Mailbox, - ); - await checkMailboxClientProperty( - 'hook', - await router.hook(), - ClientViolationType.Hook, - ); + if (config.interchainSecurityModule) { + const actual = await router.interchainSecurityModule(); + if ( + typeof config.interchainSecurityModule !== 'string' && + !this.ismFactory + ) { + throw Error( + 'ISM factory not provided to HyperlaneRouterChecker, cannot check object-based ISM config', + ); + } - const mailbox = IMailbox__factory.connect( - mailboxAddr, - this.multiProvider.getProvider(chain), - ); - const ism = await mailbox.recipientIsm(router.address); + const matches = await moduleMatchesConfig( + chain, + actual, + config.interchainSecurityModule, + this.multiProvider, + this.ismFactory?.chainMap[chain] ?? ({} as any), + ); - if ( - !this.configMap[chain].interchainSecurityModule || - this.configMap[chain].interchainSecurityModule === zeroAddress - ) { - const defaultIsm = await mailbox.defaultIsm(); - if (!eqAddress(defaultIsm, ism)) { - this.addViolation({ + if (!matches) { + const violation: ClientViolation = { chain, type: ClientViolationType.InterchainSecurityModule, contract: router, - actual: ism, - expected: zeroAddress, - }); + actual, + expected: config.interchainSecurityModule, + description: `ISM config does not match deployed ISM`, + }; + this.addViolation(violation); } - } else { - await checkMailboxClientProperty( - 'interchainSecurityModule', - ism, - ClientViolationType.InterchainSecurityModule, - ); } } diff --git a/typescript/sdk/src/router/ProxiedRouterDeployer.ts b/typescript/sdk/src/router/ProxiedRouterDeployer.ts index 335a421e2..8f4d03d2f 100644 --- a/typescript/sdk/src/router/ProxiedRouterDeployer.ts +++ b/typescript/sdk/src/router/ProxiedRouterDeployer.ts @@ -9,7 +9,6 @@ import { eqAddress } from '@hyperlane-xyz/utils'; import { HyperlaneContracts, HyperlaneFactories } from '../contracts/types.js'; import { DeployerOptions } from '../deploy/HyperlaneDeployer.js'; -import { resolveOrDeployAccountOwner } from '../deploy/types.js'; import { MultiProvider } from '../providers/MultiProvider.js'; import { ChainName } from '../types.js'; @@ -100,11 +99,7 @@ export abstract class ProxiedRouterDeployer< constants.AddressZero, this.multiProvider.getProvider(chain), ); - adminOwner = await resolveOrDeployAccountOwner( - this.multiProvider, - chain, - config.owner, - ); + adminOwner = config.owner; } await super.runIfOwner(chain, proxyAdmin, async () => { diff --git a/typescript/sdk/src/router/schemas.ts b/typescript/sdk/src/router/schemas.ts index 6c6ffa031..30fd704d2 100644 --- a/typescript/sdk/src/router/schemas.ts +++ b/typescript/sdk/src/router/schemas.ts @@ -1,12 +1,13 @@ import { z } from 'zod'; -import { OwnableConfigSchema } from '../deploy/schemas.js'; +import { HookConfigSchema } from '../hook/schemas.js'; import { IsmConfigSchema } from '../ism/schemas.js'; import { ZHash } from '../metadata/customZodTypes.js'; +import { OwnableSchema } from '../schemas.js'; -export const MailboxClientConfigSchema = OwnableConfigSchema.extend({ +export const MailboxClientConfigSchema = OwnableSchema.extend({ mailbox: ZHash, - hook: ZHash.optional(), + hook: HookConfigSchema.optional(), interchainSecurityModule: IsmConfigSchema.optional(), }); diff --git a/typescript/sdk/src/schemas.ts b/typescript/sdk/src/schemas.ts new file mode 100644 index 000000000..9e9b0ee1c --- /dev/null +++ b/typescript/sdk/src/schemas.ts @@ -0,0 +1,12 @@ +import { z } from 'zod'; + +import { ZHash } from './metadata/customZodTypes.js'; + +export const OwnableSchema = z.object({ + owner: ZHash, + ownerOverrides: z.record(ZHash).optional(), +}); + +export const PausableSchema = OwnableSchema.extend({ + paused: z.boolean(), +}); diff --git a/typescript/sdk/src/test/testUtils.ts b/typescript/sdk/src/test/testUtils.ts index fc498469a..84fc8934c 100644 --- a/typescript/sdk/src/test/testUtils.ts +++ b/typescript/sdk/src/test/testUtils.ts @@ -59,8 +59,8 @@ export function testCoreConfig( } const TEST_ORACLE_CONFIG = { - gasPrice: ethers.utils.parseUnits('1', 'gwei'), - tokenExchangeRate: ethers.utils.parseUnits('1', 10), + gasPrice: ethers.utils.parseUnits('1', 'gwei').toString(), + tokenExchangeRate: ethers.utils.parseUnits('1', 10).toString(), }; export function testIgpConfig( @@ -71,6 +71,7 @@ export function testIgpConfig( chains.map((local) => [ local, { + type: HookType.INTERCHAIN_GAS_PAYMASTER, owner, oracleKey: owner, beneficiary: owner, diff --git a/typescript/sdk/src/token/EvmERC20WarpRouteReader.ts b/typescript/sdk/src/token/EvmERC20WarpRouteReader.ts index d62285705..3055ce4bf 100644 --- a/typescript/sdk/src/token/EvmERC20WarpRouteReader.ts +++ b/typescript/sdk/src/token/EvmERC20WarpRouteReader.ts @@ -20,7 +20,7 @@ import { TokenMetadata } from './types.js'; const { AddressZero } = ethers.constants; -export class EvmWarpRouteReader { +export class EvmERC20WarpRouteReader { provider: providers.Provider; evmHookReader: EvmHookReader; evmIsmReader: EvmIsmReader; @@ -95,8 +95,9 @@ export class EvmWarpRouteReader { const derivedIsm = eqAddress(ism, AddressZero) ? undefined : await this.evmIsmReader.deriveIsmConfig(ism); - // TODO: add after https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/3667 is fixed - const derivedHook = eqAddress(hook, AddressZero) ? undefined : hook; + const derivedHook = eqAddress(hook, AddressZero) + ? undefined + : await this.evmHookReader.deriveHookConfig(hook); return { mailbox, diff --git a/typescript/sdk/src/token/deploy.hardhat-test.ts b/typescript/sdk/src/token/deploy.hardhat-test.ts index 49323191d..71ac68287 100644 --- a/typescript/sdk/src/token/deploy.hardhat-test.ts +++ b/typescript/sdk/src/token/deploy.hardhat-test.ts @@ -12,7 +12,7 @@ import { HyperlaneProxyFactoryDeployer } from '../deploy/HyperlaneProxyFactoryDe import { HyperlaneIsmFactory } from '../ism/HyperlaneIsmFactory.js'; import { MultiProvider } from '../providers/MultiProvider.js'; -import { EvmWarpRouteReader } from './EvmERC20WarpRouteReader.js'; +import { EvmERC20WarpRouteReader } from './EvmERC20WarpRouteReader.js'; import { TokenType } from './config.js'; import { HypERC20Deployer } from './deploy.js'; import { TokenRouterConfig } from './schemas.js'; @@ -70,11 +70,14 @@ describe('TokenDeployer', async () => { for (const type of [TokenType.collateral, TokenType.synthetic]) { describe('ERC20WarpRouterReader', async () => { - let reader: EvmWarpRouteReader; + let reader: EvmERC20WarpRouteReader; let routerAddress: Address; before(() => { - reader = new EvmWarpRouteReader(multiProvider, TestChainName.test1); + reader = new EvmERC20WarpRouteReader( + multiProvider, + TestChainName.test1, + ); }); beforeEach(async () => { From 6c36f9b9f1f2a7aef293191a308f580456f22ebf Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Thu, 30 May 2024 23:38:59 -0400 Subject: [PATCH 09/73] fix: cli e2e was inadvertently broken (#3873) ### Description - Fixes CLI e2e ### Drive-by changes - Use `HyperlaneCore` helpers to simplify CLI ### Backward compatibility Yes ### Testing CLI E2E matrix (now required on `cli-2.0` branch) --- typescript/cli/examples/hooks.yaml | 8 ++-- typescript/cli/src/send/message.ts | 53 +++++++------------------- typescript/cli/src/tests/hooks.test.ts | 8 ++-- 3 files changed, 22 insertions(+), 47 deletions(-) diff --git a/typescript/cli/examples/hooks.yaml b/typescript/cli/examples/hooks.yaml index cc5fbec92..d33bf0934 100644 --- a/typescript/cli/examples/hooks.yaml +++ b/typescript/cli/examples/hooks.yaml @@ -41,8 +41,8 @@ anvil1: anvil2: 50000 # gas amount (number) oracleConfig: anvil2: - gasPrice: '1000000000000000000' - tokenExchangeRate: '1000000000000000000' + gasPrice: '100' + tokenExchangeRate: '100' anvil2: required: type: protocolFee @@ -66,5 +66,5 @@ anvil2: anvil1: 50000 oracleConfig: anvil1: - gasPrice: '1000000000000000000' - tokenExchangeRate: '1000000000000000000' + gasPrice: '100' + tokenExchangeRate: '100' diff --git a/typescript/cli/src/send/message.ts b/typescript/cli/src/send/message.ts index 102592bbc..286349587 100644 --- a/typescript/cli/src/send/message.ts +++ b/typescript/cli/src/send/message.ts @@ -1,5 +1,3 @@ -import { ethers } from 'ethers'; - import { ChainName, HyperlaneCore } from '@hyperlane-xyz/sdk'; import { addressToBytes32, timeout } from '@hyperlane-xyz/utils'; @@ -81,18 +79,12 @@ async function executeDelivery({ const { registry, multiProvider } = context; const chainAddresses = await registry.getAddresses(); const core = HyperlaneCore.fromAddressesMap(chainAddresses, multiProvider); - const mailbox = core.getContracts(origin).mailbox; - let hook = chainAddresses[origin]?.customHook; + const hook = chainAddresses[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 { const recipient = chainAddresses[destination].testRecipient; if (!recipient) { @@ -100,33 +92,15 @@ async function executeDelivery({ } const formattedRecipient = addressToBytes32(recipient); - log('Getting gas quote'); - const value = await mailbox[ - 'quoteDispatch(uint32,bytes32,bytes,bytes,address)' - ]( - destinationDomain, - formattedRecipient, - messageBody, - ethers.utils.hexlify([]), - hook, - ); - log(`Paying for gas with ${value} wei`); - log('Dispatching message'); - const messageTx = await mailbox[ - 'dispatch(uint32,bytes32,bytes,bytes,address)' - ]( - destinationDomain, + const { dispatchTx, message } = await core.sendMessage( + origin, + destination, formattedRecipient, messageBody, - ethers.utils.hexlify([]), hook, - { - value, - }, + undefined, ); - txReceipt = await multiProvider.handleTx(origin, messageTx); - const message = core.getDispatchedMessages(txReceipt)[0]; logBlue(`Sent message from ${origin} to ${recipient} on ${destination}.`); logBlue(`Message ID: ${message.id}`); log(`Message: ${JSON.stringify(message)}`); @@ -135,7 +109,15 @@ async function executeDelivery({ log('Attempting self-relay of message'); await core.relayMessage(message); logGreen('Message was self-relayed!'); - return; + } else { + if (skipWaitForDelivery) { + return; + } + + log('Waiting for message delivery on destination chain...'); + // Max wait 10 minutes + await core.waitForMessageProcessed(dispatchTx, 10000, 60); + logGreen('Message was delivered!'); } } catch (e) { errorRed( @@ -143,11 +125,4 @@ async function executeDelivery({ ); throw e; } - - if (skipWaitForDelivery) return; - - log('Waiting for message delivery on destination chain...'); - // Max wait 10 minutes - await core.waitForMessageProcessed(txReceipt, 10000, 60); - logGreen('Message was delivered!'); } diff --git a/typescript/cli/src/tests/hooks.test.ts b/typescript/cli/src/tests/hooks.test.ts index 0401ce024..3cb15d84d 100644 --- a/typescript/cli/src/tests/hooks.test.ts +++ b/typescript/cli/src/tests/hooks.test.ts @@ -35,8 +35,8 @@ describe('readHooksConfigMap', () => { overhead: { anvil2: 50000 }, oracleConfig: { anvil2: { - gasPrice: '1000000000000000000', - tokenExchangeRate: '1000000000000000000', + gasPrice: '100', + tokenExchangeRate: '100', }, }, }, @@ -71,8 +71,8 @@ describe('readHooksConfigMap', () => { overhead: { anvil1: 50000 }, oracleConfig: { anvil1: { - gasPrice: '1000000000000000000', - tokenExchangeRate: '1000000000000000000', + gasPrice: '100', + tokenExchangeRate: '100', }, }, }, From b560bfc26f0f2234bfc1e88c40a3bd6ebc94d6da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Noah=20Bayindirli=20=F0=9F=A5=82?= Date: Thu, 30 May 2024 23:54:33 -0400 Subject: [PATCH 10/73] feat(cli): add 'hyperlane core config' command (#3857) ### Description * adds new `hyperlane core config` command * this combines previous `hl config create ism` & `hl config create hook` control flows * adds default ISM behaviour to aggregate `IsmType.TRUSTED_RELAYER` and the previous default fallback, `IsmType.MESSAGE_ID_MULTISIG` ### Related issues - Fixes https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/3688 ### Backward compatibility * yes ### Testing * [x] manual * [ ] ci-test --- .changeset/hip-squids-lay.md | 5 ++ .eslintrc | 1 + typescript/cli/cli.ts | 2 + typescript/cli/src/avs/stakeRegistry.ts | 2 +- typescript/cli/src/commands/config.ts | 46 +---------------- typescript/cli/src/commands/core.ts | 49 +++++++++++++++++++ typescript/cli/src/config/hooks.ts | 20 ++++---- typescript/cli/src/config/ism.ts | 47 ++++++++++++------ typescript/cli/src/utils/keys.ts | 4 +- typescript/cli/src/validator/address.ts | 6 +-- .../sdk/src/core/CoreDeployer.hardhat-test.ts | 2 +- typescript/sdk/src/hook/EvmHookModule.ts | 2 +- typescript/sdk/src/ism/EvmIsmModule.ts | 2 +- 13 files changed, 109 insertions(+), 79 deletions(-) create mode 100644 .changeset/hip-squids-lay.md create mode 100644 typescript/cli/src/commands/core.ts diff --git a/.changeset/hip-squids-lay.md b/.changeset/hip-squids-lay.md new file mode 100644 index 000000000..4162f7e76 --- /dev/null +++ b/.changeset/hip-squids-lay.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/cli': minor +--- + +Adds 'hyperlane core config'. diff --git a/.eslintrc b/.eslintrc index 0fffcede5..23ad387d6 100644 --- a/.eslintrc +++ b/.eslintrc @@ -23,6 +23,7 @@ "no-extra-boolean-cast": ["error"], "no-ex-assign": ["error"], "no-constant-condition": ["off"], + "no-return-await": ["error"], "guard-for-in": ["error"], "@typescript-eslint/ban-ts-comment": ["off"], "@typescript-eslint/explicit-module-boundary-types": ["off"], diff --git a/typescript/cli/cli.ts b/typescript/cli/cli.ts index a8b9127f3..acb310584 100644 --- a/typescript/cli/cli.ts +++ b/typescript/cli/cli.ts @@ -8,6 +8,7 @@ import './env.js'; import { avsCommand } from './src/commands/avs.js'; import { chainsCommand } from './src/commands/chains.js'; import { configCommand } from './src/commands/config.js'; +import { coreCommand } from './src/commands/core.js'; import { deployCommand } from './src/commands/deploy.js'; import { hookCommand } from './src/commands/hook.js'; import { ismCommand } from './src/commands/ism.js'; @@ -53,6 +54,7 @@ try { .command(avsCommand) .command(chainsCommand) .command(configCommand) + .command(coreCommand) .command(deployCommand) .command(hookCommand) .command(ismCommand) diff --git a/typescript/cli/src/avs/stakeRegistry.ts b/typescript/cli/src/avs/stakeRegistry.ts index 9d23bffaa..a159e8e2f 100644 --- a/typescript/cli/src/avs/stakeRegistry.ts +++ b/typescript/cli/src/avs/stakeRegistry.ts @@ -119,7 +119,7 @@ async function readOperatorFromEncryptedJson( message: 'Enter the password for the operator key file: ', }); - return await Wallet.fromEncryptedJson(encryptedJson, keyFilePassword); + return Wallet.fromEncryptedJson(encryptedJson, keyFilePassword); } async function getOperatorSignature( diff --git a/typescript/cli/src/commands/config.ts b/typescript/cli/src/commands/config.ts index bf30e78ee..8914458ee 100644 --- a/typescript/cli/src/commands/config.ts +++ b/typescript/cli/src/commands/config.ts @@ -1,12 +1,8 @@ import { CommandModule } from 'yargs'; import { createChainConfig, readChainConfigs } from '../config/chain.js'; -import { createHooksConfigMap } from '../config/hooks.js'; -import { createIsmConfigMap, readIsmConfig } from '../config/ism.js'; -import { - createMultisigConfig, - readMultisigConfig, -} from '../config/multisig.js'; +import { readIsmConfig } from '../config/ism.js'; +import { readMultisigConfig } from '../config/multisig.js'; import { createWarpRouteDeployConfig, readWarpRouteDeployConfig, @@ -40,8 +36,6 @@ const createCommand: CommandModule = { builder: (yargs) => yargs .command(createChainConfigCommand) - .command(createIsmConfigCommand) - .command(createHookConfigCommand) .command(createWarpRouteDeployConfigCommand) .version(false) .demandCommand(), @@ -57,42 +51,6 @@ const createChainConfigCommand: CommandModuleWithContext<{}> = { }, }; -const createIsmConfigCommand: CommandModuleWithContext<{ - out: string; - advanced: boolean; -}> = { - command: 'ism', - describe: 'Create a basic or advanced ISM config for a validator set', - builder: { - out: outputFileCommandOption('./configs/ism.yaml'), - advanced: { - type: 'boolean', - describe: 'Create an advanced ISM configuration', - default: false, - }, - }, - handler: async ({ context, out, advanced }) => { - if (advanced) { - await createIsmConfigMap({ context, outPath: out }); - } else { - await createMultisigConfig({ context, outPath: out }); - } - process.exit(0); - }, -}; - -const createHookConfigCommand: CommandModuleWithContext<{ out: string }> = { - command: 'hooks', - describe: 'Create a new hooks config (required & default)', - builder: { - out: outputFileCommandOption('./configs/hooks.yaml'), - }, - handler: async ({ context, out }) => { - await createHooksConfigMap({ context, outPath: out }); - process.exit(0); - }, -}; - const createWarpRouteDeployConfigCommand: CommandModuleWithContext<{ out: string; }> = { diff --git a/typescript/cli/src/commands/core.ts b/typescript/cli/src/commands/core.ts new file mode 100644 index 000000000..6947ce494 --- /dev/null +++ b/typescript/cli/src/commands/core.ts @@ -0,0 +1,49 @@ +import { CommandModule } from 'yargs'; + +import { createHooksConfigMap } from '../config/hooks.js'; +import { createIsmConfigMap } from '../config/ism.js'; +import { CommandModuleWithContext } from '../context/types.js'; +import { log } from '../logger.js'; + +import { outputFileCommandOption } from './options.js'; + +/** + * Parent command + */ +export const coreCommand: CommandModule = { + command: 'core', + describe: 'Manage core Hyperlane contracts & configs', + builder: (yargs) => yargs.command(config).version(false).demandCommand(), + handler: () => log('Command required'), +}; + +export const config: CommandModuleWithContext<{ + ismAdvanced: boolean; + ismOut: string; + hooksOut: string; +}> = { + command: 'config', + describe: 'Create a core configuration, including ISMs and hooks.', + builder: { + ismAdvanced: { + type: 'boolean', + describe: 'Create an advanced ISM & hook configuration', + default: false, + }, + ismOut: outputFileCommandOption('./configs/ism.yaml'), + hooksOut: outputFileCommandOption('./configs/hooks.yaml'), + }, + handler: async ({ context, ismAdvanced, ismOut, hooksOut }) => { + await createIsmConfigMap({ + context, + outPath: ismOut, + shouldUseDefault: !ismAdvanced, + }); + await createHooksConfigMap({ + context, + outPath: hooksOut, + }); + + process.exit(0); + }, +}; diff --git a/typescript/cli/src/config/hooks.ts b/typescript/cli/src/config/hooks.ts index d7fb61979..01103dffd 100644 --- a/typescript/cli/src/config/hooks.ts +++ b/typescript/cli/src/config/hooks.ts @@ -78,7 +78,7 @@ export async function createHooksConfigMap({ const result: HooksConfigMap = {}; for (const chain of chains) { for (const hookRequirements of ['required', 'default']) { - log(`Setting ${hookRequirements} hook for chain ${chain}`); + log(`\nSet ${hookRequirements} hook for chain ${chain}:`); const remotes = chains.filter((c) => c !== chain); result[chain] = { ...result[chain], @@ -159,7 +159,7 @@ export async function createProtocolFeeConfig( chain: ChainName, ): Promise { const owner = await input({ - message: 'Enter owner address', + message: 'Enter owner address for protocol fee hook', }); const ownerAddress = normalizeAddressEvm(owner); let beneficiary; @@ -171,7 +171,7 @@ export async function createProtocolFeeConfig( beneficiary = ownerAddress; } else { beneficiary = await input({ - message: 'Enter beneficiary address', + message: 'Enter beneficiary address for protocol fee hook', }); } const beneficiaryAddress = normalizeAddressEvm(beneficiary); @@ -181,7 +181,7 @@ export async function createProtocolFeeConfig( message: `Enter max protocol fee ${nativeTokenAndDecimals( context, chain, - )} e.g. 1.0)`, + )} e.g. 1.0) for protocol fee hook`, }), ); const protocolFee = toWei( @@ -189,7 +189,7 @@ export async function createProtocolFeeConfig( message: `Enter protocol fee in ${nativeTokenAndDecimals( context, chain, - )} e.g. 0.01)`, + )} e.g. 0.01) for protocol fee hook`, }), ); if (BigNumberJs(protocolFee).gt(maxProtocolFee)) { @@ -211,7 +211,7 @@ export async function createIGPConfig( remotes: ChainName[], ): Promise { const owner = await input({ - message: 'Enter owner address', + message: 'Enter owner address for IGP hook', }); const ownerAddress = normalizeAddressEvm(owner); let beneficiary, oracleKey; @@ -224,10 +224,10 @@ export async function createIGPConfig( oracleKey = ownerAddress; } else { beneficiary = await input({ - message: 'Enter beneficiary address', + message: 'Enter beneficiary address for IGP hook', }); oracleKey = await input({ - message: 'Enter gasOracleKey address', + message: 'Enter gasOracleKey address for IGP hook', }); } const beneficiaryAddress = normalizeAddressEvm(beneficiary); @@ -236,7 +236,7 @@ export async function createIGPConfig( for (const chain of remotes) { const overhead = parseInt( await input({ - message: `Enter overhead for ${chain} (eg 75000)`, + message: `Enter overhead for ${chain} (eg 75000) for IGP hook`, }), ); overheads[chain] = overhead; @@ -279,7 +279,7 @@ export async function createRoutingConfig( remotes: ChainName[], ): Promise { const owner = await input({ - message: 'Enter owner address', + message: 'Enter owner address for routing ISM', }); const ownerAddress = owner; diff --git a/typescript/cli/src/config/ism.ts b/typescript/cli/src/config/ism.ts index 17780f2ad..9269b7263 100644 --- a/typescript/cli/src/config/ism.ts +++ b/typescript/cli/src/config/ism.ts @@ -21,7 +21,10 @@ import { logGreen, logRed, } from '../logger.js'; -import { runMultiChainSelectionStep } from '../utils/chains.js'; +import { + detectAndConfirmOrPrompt, + runMultiChainSelectionStep, +} from '../utils/chains.js'; import { mergeYamlOrJson, readYamlOrJson } from '../utils/files.js'; const IsmConfigMapSchema = z.record(IsmConfigSchema).refine( @@ -73,9 +76,11 @@ export function isValildIsmConfig(config: any) { export async function createIsmConfigMap({ context, outPath, + shouldUseDefault = true, }: { context: CommandContext; outPath: string; + shouldUseDefault: boolean; }) { logBlue('Creating a new advanced ISM config'); logBoldUnderlinedRed('WARNING: USE AT YOUR RISK.'); @@ -90,8 +95,10 @@ export async function createIsmConfigMap({ const result: IsmConfigMap = {}; for (const chain of chains) { - log(`Setting values for chain ${chain}`); - result[chain] = await createIsmConfig(chain, chains); + log(`\nSet values for chain ${chain}:`); + result[chain] = shouldUseDefault + ? await createTrustedRelayerConfig(context) + : await createIsmConfig(context, chain, chains); // TODO consider re-enabling. Disabling based on feedback from @nambrot for now. // repeat = await confirm({ @@ -128,6 +135,7 @@ const ISM_TYPE_DESCRIPTIONS: Record = { }; export async function createIsmConfig( + context: CommandContext, remote: ChainName, origins: ChainName[], ): Promise { @@ -149,13 +157,13 @@ export async function createIsmConfig( moduleType === IsmType.ROUTING || moduleType === IsmType.FALLBACK_ROUTING ) { - return createRoutingConfig(moduleType, remote, origins); + return createRoutingConfig(context, moduleType, remote, origins); } else if (moduleType === IsmType.AGGREGATION) { - return createAggregationConfig(remote, origins); + return createAggregationConfig(context, remote, origins); } else if (moduleType === IsmType.TEST_ISM) { return { type: IsmType.TEST_ISM }; } else if (moduleType === IsmType.TRUSTED_RELAYER) { - return createTrustedRelayerConfig(); + return createTrustedRelayerConfig(context); } throw new Error(`Invalid ISM type: ${moduleType}}`); @@ -165,12 +173,13 @@ export async function createMultisigConfig( type: IsmType.MERKLE_ROOT_MULTISIG | IsmType.MESSAGE_ID_MULTISIG, ): Promise { const thresholdInput = await input({ - message: 'Enter threshold of validators (number)', + message: 'Enter threshold of validators (number) for multisig ISM', }); const threshold = parseInt(thresholdInput, 10); const validatorsInput = await input({ - message: 'Enter validator addresses (comma separated list)', + message: + 'Enter validator addresses (comma separated list) for multisig ISM', }); const validators = validatorsInput.split(',').map((v) => v.trim()); return { @@ -180,10 +189,14 @@ export async function createMultisigConfig( }; } -async function createTrustedRelayerConfig(): Promise { - const relayer = await input({ - message: 'Enter relayer address', - }); +async function createTrustedRelayerConfig( + context: CommandContext, +): Promise { + const relayer = await detectAndConfirmOrPrompt( + async () => context.signer?.getAddress(), + 'For trusted relayer ISM, enter', + 'relayer address', + ); return { type: IsmType.TRUSTED_RELAYER, relayer, @@ -191,6 +204,7 @@ async function createTrustedRelayerConfig(): Promise { } export async function createAggregationConfig( + context: CommandContext, remote: ChainName, chains: ChainName[], ): Promise { @@ -203,14 +217,14 @@ export async function createAggregationConfig( const threshold = parseInt( await input({ - message: 'Enter the threshold of ISMs to for verification (number)', + message: 'Enter the threshold of ISMs for verification (number)', }), 10, ); const modules: Array = []; for (let i = 0; i < isms; i++) { - modules.push(await createIsmConfig(remote, chains)); + modules.push(await createIsmConfig(context, remote, chains)); } return { type: IsmType.AGGREGATION, @@ -220,12 +234,13 @@ export async function createAggregationConfig( } export async function createRoutingConfig( + context: CommandContext, type: IsmType.ROUTING | IsmType.FALLBACK_ROUTING, remote: ChainName, chains: ChainName[], ): Promise { const owner = await input({ - message: 'Enter owner address', + message: 'Enter owner address for routing ISM', }); const ownerAddress = owner; const origins = chains.filter((chain) => chain !== remote); @@ -235,7 +250,7 @@ export async function createRoutingConfig( await confirm({ message: `You are about to configure ISM from source chain ${chain}. Continue?`, }); - const config = await createIsmConfig(chain, chains); + const config = await createIsmConfig(context, chain, chains); domainsMap[chain] = config; } return { diff --git a/typescript/cli/src/utils/keys.ts b/typescript/cli/src/utils/keys.ts index a7d2abb26..552b8d53d 100644 --- a/typescript/cli/src/utils/keys.ts +++ b/typescript/cli/src/utils/keys.ts @@ -68,7 +68,7 @@ async function addressToImpersonatedSigner( if (address.length != ETHEREUM_ADDRESS_LENGTH) throw new Error('Invalid address length.'); else if (ethers.utils.isHexString(ensure0x(formattedKey))) - return await impersonateAccount(address); + return impersonateAccount(address); else throw new Error('Invalid address format'); } @@ -93,7 +93,7 @@ async function retrieveKey( ): Promise { if (skipConfirmation) throw new Error(`No private key provided`); else - return await input({ + return input({ message: `Please enter private key or use the HYP_KEY environment variable.`, }); } diff --git a/typescript/cli/src/validator/address.ts b/typescript/cli/src/validator/address.ts index d816fcb1f..a0a2cc4ea 100644 --- a/typescript/cli/src/validator/address.ts +++ b/typescript/cli/src/validator/address.ts @@ -141,7 +141,7 @@ function getEthereumAddress(publicKey: Buffer): string { async function getAccessKeyId(skipConfirmation: boolean) { if (skipConfirmation) throw new Error('No AWS access key ID set.'); else - return await input({ + return input({ message: 'Please enter AWS access key ID or use the AWS_ACCESS_KEY_ID environment variable.', }); @@ -150,7 +150,7 @@ async function getAccessKeyId(skipConfirmation: boolean) { async function getSecretAccessKey(skipConfirmation: boolean) { if (skipConfirmation) throw new Error('No AWS secret access key set.'); else - return await input({ + return input({ message: 'Please enter AWS secret access key or use the AWS_SECRET_ACCESS_KEY environment variable.', }); @@ -159,7 +159,7 @@ async function getSecretAccessKey(skipConfirmation: boolean) { async function getRegion(skipConfirmation: boolean) { if (skipConfirmation) throw new Error('No AWS region set.'); else - return await input({ + return input({ message: 'Please enter AWS region or use the AWS_REGION environment variable.', }); diff --git a/typescript/sdk/src/core/CoreDeployer.hardhat-test.ts b/typescript/sdk/src/core/CoreDeployer.hardhat-test.ts index d4697599c..2664d0ee2 100644 --- a/typescript/sdk/src/core/CoreDeployer.hardhat-test.ts +++ b/typescript/sdk/src/core/CoreDeployer.hardhat-test.ts @@ -127,7 +127,7 @@ describe('core', async () => { }); async function deriveCoreConfig(chainName: string, mailboxAddress: string) { - return await new EvmCoreReader(multiProvider, chainName).deriveCoreConfig( + return new EvmCoreReader(multiProvider, chainName).deriveCoreConfig( mailboxAddress, ); } diff --git a/typescript/sdk/src/hook/EvmHookModule.ts b/typescript/sdk/src/hook/EvmHookModule.ts index c6e01bc87..4ed3cca1f 100644 --- a/typescript/sdk/src/hook/EvmHookModule.ts +++ b/typescript/sdk/src/hook/EvmHookModule.ts @@ -39,7 +39,7 @@ export class EvmHookModule extends HyperlaneModule< } public async read(): Promise { - return await this.reader.deriveHookConfig(this.args.addresses.deployedHook); + return this.reader.deriveHookConfig(this.args.addresses.deployedHook); } public async update(_config: HookConfig): Promise { diff --git a/typescript/sdk/src/ism/EvmIsmModule.ts b/typescript/sdk/src/ism/EvmIsmModule.ts index c82b4194d..aa1b41d54 100644 --- a/typescript/sdk/src/ism/EvmIsmModule.ts +++ b/typescript/sdk/src/ism/EvmIsmModule.ts @@ -39,7 +39,7 @@ export class EvmIsmModule extends HyperlaneModule< } public async read(): Promise { - return await this.reader.deriveIsmConfig(this.args.addresses.deployedIsm); + return this.reader.deriveIsmConfig(this.args.addresses.deployedIsm); } public async update(_config: IsmConfig): Promise { From 84bc0bd7f18606d8f8463dccaaf9afd145ce8387 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Noah=20Bayindirli=20=F0=9F=A5=82?= Date: Fri, 31 May 2024 00:51:24 -0400 Subject: [PATCH 11/73] feat(cli): add 'hyperlane warp config' command (#3866) ### Description * adds new `hyperlane warp config` command * this replaces previous `hl config create warp` control flow ### Related issues - fixes https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/3537 ### Backward compatibility * yes ### Testing * [x] manual * [ ] ci-test --- .changeset/great-cycles-tickle.md | 5 ++++ typescript/cli/cli.ts | 2 ++ typescript/cli/src/commands/config.ts | 27 ++---------------- typescript/cli/src/commands/warp.ts | 41 +++++++++++++++++++++++++++ typescript/cli/src/config/ism.ts | 2 +- typescript/cli/src/config/warp.ts | 38 ++++++++++++++++++++++++- 6 files changed, 89 insertions(+), 26 deletions(-) create mode 100644 .changeset/great-cycles-tickle.md create mode 100644 typescript/cli/src/commands/warp.ts diff --git a/.changeset/great-cycles-tickle.md b/.changeset/great-cycles-tickle.md new file mode 100644 index 000000000..2c7661bc7 --- /dev/null +++ b/.changeset/great-cycles-tickle.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/cli': minor +--- + +Adds 'hyperlane warp config'. diff --git a/typescript/cli/cli.ts b/typescript/cli/cli.ts index acb310584..708dfcaef 100644 --- a/typescript/cli/cli.ts +++ b/typescript/cli/cli.ts @@ -23,6 +23,7 @@ import { import { sendCommand } from './src/commands/send.js'; import { statusCommand } from './src/commands/status.js'; import { validatorCommand } from './src/commands/validator.js'; +import { warpCommand } from './src/commands/warp.js'; import { contextMiddleware } from './src/context/context.js'; import { configureLogger, errorRed } from './src/logger.js'; import { checkVersion } from './src/utils/version-check.js'; @@ -61,6 +62,7 @@ try { .command(sendCommand) .command(statusCommand) .command(validatorCommand) + .command(warpCommand) .version(VERSION) .demandCommand() .strict() diff --git a/typescript/cli/src/commands/config.ts b/typescript/cli/src/commands/config.ts index 8914458ee..2dd532249 100644 --- a/typescript/cli/src/commands/config.ts +++ b/typescript/cli/src/commands/config.ts @@ -3,14 +3,11 @@ import { CommandModule } from 'yargs'; import { createChainConfig, readChainConfigs } from '../config/chain.js'; import { readIsmConfig } from '../config/ism.js'; import { readMultisigConfig } from '../config/multisig.js'; -import { - createWarpRouteDeployConfig, - readWarpRouteDeployConfig, -} from '../config/warp.js'; +import { readWarpRouteDeployConfig } from '../config/warp.js'; import { CommandModuleWithContext } from '../context/types.js'; import { log, logGreen } from '../logger.js'; -import { inputFileCommandOption, outputFileCommandOption } from './options.js'; +import { inputFileCommandOption } from './options.js'; /** * Parent command @@ -34,11 +31,7 @@ const createCommand: CommandModule = { command: 'create', describe: 'Create a new Hyperlane config', builder: (yargs) => - yargs - .command(createChainConfigCommand) - .command(createWarpRouteDeployConfigCommand) - .version(false) - .demandCommand(), + yargs.command(createChainConfigCommand).version(false).demandCommand(), handler: () => log('Command required'), }; @@ -51,20 +44,6 @@ const createChainConfigCommand: CommandModuleWithContext<{}> = { }, }; -const createWarpRouteDeployConfigCommand: CommandModuleWithContext<{ - out: string; -}> = { - command: 'warp', - describe: 'Create a new Warp Route deployment config', - builder: { - out: outputFileCommandOption('./configs/warp-route-deployment.yaml'), - }, - handler: async ({ context, out }) => { - await createWarpRouteDeployConfig({ context, outPath: out }); - process.exit(0); - }, -}; - /** * Validate commands */ diff --git a/typescript/cli/src/commands/warp.ts b/typescript/cli/src/commands/warp.ts new file mode 100644 index 000000000..d568fe2c1 --- /dev/null +++ b/typescript/cli/src/commands/warp.ts @@ -0,0 +1,41 @@ +import { CommandModule } from 'yargs'; + +import { createWarpRouteDeployConfig } from '../config/warp.js'; +import { CommandModuleWithContext } from '../context/types.js'; +import { log } from '../logger.js'; + +import { outputFileCommandOption } from './options.js'; + +/** + * Parent command + */ +export const warpCommand: CommandModule = { + command: 'warp', + describe: 'Manage Hyperlane warp routes', + builder: (yargs) => yargs.command(config).version(false).demandCommand(), + handler: () => log('Command required'), +}; + +export const config: CommandModuleWithContext<{ + ismAdvanced: boolean; + out: string; +}> = { + command: 'config', + describe: 'Create a warp route configuration.', + builder: { + ismAdvanced: { + type: 'boolean', + describe: 'Create an advanced ISM & hook configuration', + default: false, + }, + out: outputFileCommandOption('./configs/warp-route-deployment.yaml'), + }, + handler: async ({ context, ismAdvanced, out }) => { + await createWarpRouteDeployConfig({ + context, + outPath: out, + shouldUseDefault: !ismAdvanced, + }); + process.exit(0); + }, +}; diff --git a/typescript/cli/src/config/ism.ts b/typescript/cli/src/config/ism.ts index 9269b7263..d2252fc62 100644 --- a/typescript/cli/src/config/ism.ts +++ b/typescript/cli/src/config/ism.ts @@ -189,7 +189,7 @@ export async function createMultisigConfig( }; } -async function createTrustedRelayerConfig( +export async function createTrustedRelayerConfig( context: CommandContext, ): Promise { const relayer = await detectAndConfirmOrPrompt( diff --git a/typescript/cli/src/config/warp.ts b/typescript/cli/src/config/warp.ts index d9ca9b4e5..fabb355d7 100644 --- a/typescript/cli/src/config/warp.ts +++ b/typescript/cli/src/config/warp.ts @@ -2,6 +2,9 @@ import { input, select } from '@inquirer/prompts'; import { ChainMap, + ChainName, + IsmConfig, + IsmType, MailboxClientConfig, TokenType, WarpCoreConfig, @@ -19,6 +22,12 @@ import { } from '../utils/chains.js'; import { readYamlOrJson, writeYamlOrJson } from '../utils/files.js'; +import { + createIsmConfig, + createRoutingConfig, + createTrustedRelayerConfig, +} from './ism.js'; + const TYPE_DESCRIPTIONS: Record = { [TokenType.synthetic]: 'A new ERC20 with remote transfer functionality', [TokenType.collateral]: @@ -94,9 +103,11 @@ export function isValidWarpRouteDeployConfig(config: any) { export async function createWarpRouteDeployConfig({ context, outPath, + shouldUseDefault = false, }: { context: CommandContext; outPath: string; + shouldUseDefault: boolean; }) { logBlue('Creating a new warp route deployment config'); @@ -132,6 +143,10 @@ export async function createWarpRouteDeployConfig({ 'mailbox address', ); + const interchainSecurityModule = shouldUseDefault + ? await createDefaultWarpIsmConfig(context, chain) + : await createIsmConfig(context, chain, warpChains); + switch (type) { case TokenType.collateral: case TokenType.XERC20: @@ -145,13 +160,20 @@ export async function createWarpRouteDeployConfig({ type, owner, isNft, + interchainSecurityModule, token: await input({ message: `Enter the existing token address on chain ${chain}`, }), }; break; default: - result[chain] = { mailbox, type, owner, isNft }; + result[chain] = { + mailbox, + type, + owner, + isNft, + interchainSecurityModule, + }; } } @@ -174,3 +196,17 @@ export function readWarpRouteConfig(filePath: string): WarpCoreConfig { if (!config) throw new Error(`No warp route config found at ${filePath}`); return WarpCoreConfigSchema.parse(config); } + +async function createDefaultWarpIsmConfig( + context: CommandContext, + remote: ChainName, +): Promise { + return { + type: IsmType.AGGREGATION, + modules: [ + await createTrustedRelayerConfig(context), + await createRoutingConfig(context, IsmType.FALLBACK_ROUTING, remote, []), + ], + threshold: 1, + }; +} From 341b8affd577905d75f949737524743dcd812ec2 Mon Sep 17 00:00:00 2001 From: Paul Balaji Date: Fri, 31 May 2024 14:06:54 +0100 Subject: [PATCH 12/73] feat: implement `update()` for `EvmIsmModule` (#3759) --- .changeset/orange-lemons-shake.md | 7 + .../config/environments/test/multisigIsm.ts | 16 +- typescript/sdk/src/consts/testChains.ts | 15 + .../sdk/src/core/AbstractHyperlaneModule.ts | 6 +- typescript/sdk/src/core/EvmCoreModule.ts | 8 +- typescript/sdk/src/core/EvmIcaModule.ts | 8 +- typescript/sdk/src/core/HyperlaneCore.ts | 18 +- .../sdk/src/deploy/HyperlaneDeployer.ts | 8 + typescript/sdk/src/hook/EvmHookModule.ts | 8 +- typescript/sdk/src/ism/EvmIsmCreator.ts | 557 --------------- .../sdk/src/ism/EvmIsmModule.hardhat-test.ts | 434 ++++++++++++ typescript/sdk/src/ism/EvmIsmModule.ts | 643 +++++++++++++++++- .../ism/HyperlaneIsmFactory.hardhat-test.ts | 2 +- typescript/sdk/src/ism/types.ts | 7 + typescript/sdk/src/ism/utils.ts | 41 ++ typescript/sdk/src/providers/MultiProvider.ts | 9 +- typescript/sdk/src/providers/ProviderType.ts | 19 +- typescript/sdk/src/test/testUtils.ts | 4 + typescript/sdk/src/utils/logUtils.ts | 21 + typescript/utils/src/index.ts | 2 + typescript/utils/src/objects.ts | 32 +- 21 files changed, 1234 insertions(+), 631 deletions(-) create mode 100644 .changeset/orange-lemons-shake.md delete mode 100644 typescript/sdk/src/ism/EvmIsmCreator.ts create mode 100644 typescript/sdk/src/ism/EvmIsmModule.hardhat-test.ts create mode 100644 typescript/sdk/src/utils/logUtils.ts diff --git a/.changeset/orange-lemons-shake.md b/.changeset/orange-lemons-shake.md new file mode 100644 index 000000000..8e97e51a1 --- /dev/null +++ b/.changeset/orange-lemons-shake.md @@ -0,0 +1,7 @@ +--- +'@hyperlane-xyz/infra': minor +'@hyperlane-xyz/utils': minor +'@hyperlane-xyz/sdk': minor +--- + +Completes the EvmIsmModule for creating, reading and updating ISMs. diff --git a/typescript/infra/config/environments/test/multisigIsm.ts b/typescript/infra/config/environments/test/multisigIsm.ts index afa7916c9..733f5907d 100644 --- a/typescript/infra/config/environments/test/multisigIsm.ts +++ b/typescript/infra/config/environments/test/multisigIsm.ts @@ -1,11 +1,18 @@ -import { ChainMap, IsmType, MultisigIsmConfig } from '@hyperlane-xyz/sdk'; +import { + ChainMap, + IsmType, + MultisigIsmConfig, + TestChainName, +} from '@hyperlane-xyz/sdk'; +import { Address } from '@hyperlane-xyz/utils'; // the addresses here must line up with the e2e test's validator addresses -// Validators are anvil accounts 4-6 -export const chainToValidator: Record = { +// Validators are anvil accounts 4-7 +export const chainToValidator: Record = { test1: '0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65', test2: '0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc', test3: '0x976EA74026E726554dB657fA54763abd0C3a0aa9', + test4: '0x14dC79964da2C08b23698B3D3cc7Ca32193d9955', }; export const merkleRootMultisig = (validatorKey: string): MultisigIsmConfig => { @@ -26,8 +33,9 @@ export const messageIdMultisig = (validatorKey: string): MultisigIsmConfig => { // the addresses here must line up with the e2e test's validator addresses export const multisigIsm: ChainMap = { - // Validators are anvil accounts 4-6 + // Validators are anvil accounts 4-7 test1: messageIdMultisig(chainToValidator['test1']), test2: merkleRootMultisig(chainToValidator['test2']), test3: messageIdMultisig(chainToValidator['test3']), + test4: messageIdMultisig(chainToValidator['test4']), }; diff --git a/typescript/sdk/src/consts/testChains.ts b/typescript/sdk/src/consts/testChains.ts index 273796726..878de285a 100644 --- a/typescript/sdk/src/consts/testChains.ts +++ b/typescript/sdk/src/consts/testChains.ts @@ -10,6 +10,7 @@ export enum TestChainName { test1 = 'test1', test2 = 'test2', test3 = 'test3', + test4 = 'test4', } export const testChains: Array = Object.values(TestChainName); @@ -65,10 +66,24 @@ export const test3: ChainMetadata = { name: 'test3', }; +export const test4: ChainMetadata = { + ...test1, + blocks: { + confirmations: 1, + estimateBlockTime: 3, + reorgPeriod: 2, + }, + chainId: 31337, + displayName: 'Test 4', + domainId: 31337, + name: 'test4', +}; + export const testChainMetadata: ChainMap = { test1, test2, test3, + test4, }; export const testCosmosChain: ChainMetadata = { diff --git a/typescript/sdk/src/core/AbstractHyperlaneModule.ts b/typescript/sdk/src/core/AbstractHyperlaneModule.ts index a5159ac85..b4a42ec30 100644 --- a/typescript/sdk/src/core/AbstractHyperlaneModule.ts +++ b/typescript/sdk/src/core/AbstractHyperlaneModule.ts @@ -5,7 +5,7 @@ import { Annotated, ProtocolType } from '@hyperlane-xyz/utils'; import { ProtocolTypedTransaction } from '../providers/ProviderType.js'; import { ChainNameOrId } from '../types.js'; -export type HyperlaneModuleArgs< +export type HyperlaneModuleParams< TConfig, TAddressMap extends Record, > = { @@ -22,7 +22,7 @@ export abstract class HyperlaneModule< protected abstract readonly logger: Logger; protected constructor( - protected readonly args: HyperlaneModuleArgs, + protected readonly args: HyperlaneModuleParams, ) {} public serialize(): TAddressMap { @@ -32,7 +32,7 @@ export abstract class HyperlaneModule< public abstract read(): Promise; public abstract update( config: TConfig, - ): Promise[]>>; + ): Promise['transaction'][]>>; // /* // Types and static methods can be challenging. Ensure each implementation includes a static create function. diff --git a/typescript/sdk/src/core/EvmCoreModule.ts b/typescript/sdk/src/core/EvmCoreModule.ts index 113a15248..18c9fd39d 100644 --- a/typescript/sdk/src/core/EvmCoreModule.ts +++ b/typescript/sdk/src/core/EvmCoreModule.ts @@ -14,12 +14,12 @@ import { } from '../deploy/contracts.js'; import { HyperlaneIsmFactory } from '../ism/HyperlaneIsmFactory.js'; import { MultiProvider } from '../providers/MultiProvider.js'; -import { EthersV5Transaction } from '../providers/ProviderType.js'; +import { AnnotatedEV5Transaction } from '../providers/ProviderType.js'; import { ChainNameOrId } from '../types.js'; import { HyperlaneModule, - HyperlaneModuleArgs, + HyperlaneModuleParams, } from './AbstractHyperlaneModule.js'; import { EvmCoreReader } from './EvmCoreReader.js'; import { EvmIcaModule } from './EvmIcaModule.js'; @@ -45,7 +45,7 @@ export class EvmCoreModule extends HyperlaneModule< protected constructor( protected readonly multiProvider: MultiProvider, - args: HyperlaneModuleArgs, + args: HyperlaneModuleParams, ) { super(args); this.coreReader = new EvmCoreReader(multiProvider, this.args.chain); @@ -60,7 +60,7 @@ export class EvmCoreModule extends HyperlaneModule< return this.coreReader.deriveCoreConfig(this.args.addresses.mailbox); } - public async update(_config: CoreConfig): Promise { + public async update(_config: CoreConfig): Promise { throw new Error('Method not implemented.'); } diff --git a/typescript/sdk/src/core/EvmIcaModule.ts b/typescript/sdk/src/core/EvmIcaModule.ts index 47bff2ede..c94e289e9 100644 --- a/typescript/sdk/src/core/EvmIcaModule.ts +++ b/typescript/sdk/src/core/EvmIcaModule.ts @@ -5,13 +5,13 @@ import { HyperlaneAddresses } from '../contracts/types.js'; import { InterchainAccountDeployer } from '../middleware/account/InterchainAccountDeployer.js'; import { InterchainAccountFactories } from '../middleware/account/contracts.js'; import { MultiProvider } from '../providers/MultiProvider.js'; -import { EthersV5Transaction } from '../providers/ProviderType.js'; +import { AnnotatedEV5Transaction } from '../providers/ProviderType.js'; import { ProxiedRouterConfig } from '../router/types.js'; import { ChainNameOrId } from '../types.js'; import { HyperlaneModule, - HyperlaneModuleArgs, + HyperlaneModuleParams, } from './AbstractHyperlaneModule.js'; export type InterchainAccountConfig = ProxiedRouterConfig; @@ -25,7 +25,7 @@ export class EvmIcaModule extends HyperlaneModule< protected constructor( protected readonly multiProvider: MultiProvider, - args: HyperlaneModuleArgs< + args: HyperlaneModuleParams< InterchainAccountConfig, HyperlaneAddresses >, @@ -39,7 +39,7 @@ export class EvmIcaModule extends HyperlaneModule< public async update( _config: InterchainAccountConfig, - ): Promise { + ): Promise { throw new Error('Method not implemented.'); } diff --git a/typescript/sdk/src/core/HyperlaneCore.ts b/typescript/sdk/src/core/HyperlaneCore.ts index 92f0e3023..12d20c414 100644 --- a/typescript/sdk/src/core/HyperlaneCore.ts +++ b/typescript/sdk/src/core/HyperlaneCore.ts @@ -26,6 +26,7 @@ import { IsmType, ModuleType, ismTypeToModuleType } from '../ism/types.js'; import { MultiProvider } from '../providers/MultiProvider.js'; import { RouterConfig } from '../router/types.js'; import { ChainMap, ChainName } from '../types.js'; +import { findMatchingLogEvents } from '../utils/logUtils.js'; import { CoreFactories, coreFactories } from './contracts.js'; import { DispatchedMessage } from './types.js'; @@ -283,18 +284,11 @@ export class HyperlaneCore extends HyperlaneApp { sourceTx: ethers.ContractReceipt | ViemTxReceipt, ): DispatchedMessage[] { const mailbox = Mailbox__factory.createInterface(); - const dispatchLogs = sourceTx.logs - .map((log) => { - try { - return mailbox.parseLog(log); - } catch (e) { - return undefined; - } - }) - .filter( - (log): log is ethers.utils.LogDescription => - !!log && log.name === 'Dispatch', - ); + const dispatchLogs = findMatchingLogEvents( + sourceTx.logs, + mailbox, + 'Dispatch', + ); return dispatchLogs.map((log) => { const message = log.args['message']; const parsed = parseMessage(message); diff --git a/typescript/sdk/src/deploy/HyperlaneDeployer.ts b/typescript/sdk/src/deploy/HyperlaneDeployer.ts index 91815b504..dab04a3d7 100644 --- a/typescript/sdk/src/deploy/HyperlaneDeployer.ts +++ b/typescript/sdk/src/deploy/HyperlaneDeployer.ts @@ -104,6 +104,14 @@ export abstract class HyperlaneDeployer< this.cachedAddresses = addressesMap; } + async verifyContract( + chain: ChainName, + input: ContractVerificationInput, + logger = this.logger, + ): Promise { + return this.options.contractVerifier?.verifyContract(chain, input, logger); + } + abstract deployContracts( chain: ChainName, config: Config, diff --git a/typescript/sdk/src/hook/EvmHookModule.ts b/typescript/sdk/src/hook/EvmHookModule.ts index 4ed3cca1f..517666f4e 100644 --- a/typescript/sdk/src/hook/EvmHookModule.ts +++ b/typescript/sdk/src/hook/EvmHookModule.ts @@ -3,11 +3,11 @@ import { Address, ProtocolType, rootLogger } from '@hyperlane-xyz/utils'; import { HyperlaneAddresses } from '../contracts/types.js'; import { HyperlaneModule, - HyperlaneModuleArgs, + HyperlaneModuleParams, } from '../core/AbstractHyperlaneModule.js'; import { HyperlaneDeployer } from '../deploy/HyperlaneDeployer.js'; import { MultiProvider } from '../providers/MultiProvider.js'; -import { EthersV5Transaction } from '../providers/ProviderType.js'; +import { AnnotatedEV5Transaction } from '../providers/ProviderType.js'; import { EvmHookReader } from './EvmHookReader.js'; import { HookFactories } from './contracts.js'; @@ -27,7 +27,7 @@ export class EvmHookModule extends HyperlaneModule< protected constructor( protected readonly multiProvider: MultiProvider, protected readonly deployer: HyperlaneDeployer, - args: HyperlaneModuleArgs< + args: HyperlaneModuleParams< HookConfig, HyperlaneAddresses & { deployedHook: Address; @@ -42,7 +42,7 @@ export class EvmHookModule extends HyperlaneModule< return this.reader.deriveHookConfig(this.args.addresses.deployedHook); } - public async update(_config: HookConfig): Promise { + public async update(_config: HookConfig): Promise { throw new Error('Method not implemented.'); } diff --git a/typescript/sdk/src/ism/EvmIsmCreator.ts b/typescript/sdk/src/ism/EvmIsmCreator.ts deleted file mode 100644 index f4f96736b..000000000 --- a/typescript/sdk/src/ism/EvmIsmCreator.ts +++ /dev/null @@ -1,557 +0,0 @@ -import { ethers } from 'ethers'; -import { Logger } from 'pino'; - -import { - DefaultFallbackRoutingIsm, - DefaultFallbackRoutingIsm__factory, - DomainRoutingIsm, - DomainRoutingIsm__factory, - IAggregationIsm, - IAggregationIsm__factory, - IInterchainSecurityModule__factory, - IMultisigIsm, - IMultisigIsm__factory, - IRoutingIsm, - OPStackIsm__factory, - PausableIsm__factory, - StaticAddressSetFactory, - StaticThresholdAddressSetFactory, - TestIsm__factory, - TrustedRelayerIsm__factory, -} from '@hyperlane-xyz/core'; -import { - Address, - Domain, - assert, - eqAddress, - objFilter, - rootLogger, -} from '@hyperlane-xyz/utils'; - -import { HyperlaneContracts } from '../contracts/types.js'; -import { HyperlaneDeployer } from '../deploy/HyperlaneDeployer.js'; -import { ProxyFactoryFactories } from '../deploy/contracts.js'; -import { MultiProvider } from '../providers/MultiProvider.js'; -import { ChainMap, ChainName } from '../types.js'; - -import { - AggregationIsmConfig, - DeployedIsm, - DeployedIsmType, - IsmConfig, - IsmType, - MultisigIsmConfig, - RoutingIsmConfig, - RoutingIsmDelta, -} from './types.js'; -import { routingModuleDelta } from './utils.js'; - -export class EvmIsmCreator { - protected readonly logger = rootLogger.child({ module: 'EvmIsmCreator' }); - - constructor( - protected readonly deployer: HyperlaneDeployer, - protected readonly multiProvider: MultiProvider, - protected readonly factories: HyperlaneContracts, - ) {} - - async update(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(destination), - ); - } - - const ismType = config.type; - const logger = this.logger.child({ destination, ismType }); - - logger.debug( - `Updating ${ismType} on ${destination} ${ - origin ? `(for verifying ${origin})` : '' - }`, - ); - - let contract: DeployedIsmType[typeof ismType]; - switch (ismType) { - case IsmType.ROUTING: - case IsmType.FALLBACK_ROUTING: - contract = await this.updateRoutingIsm({ - destination, - config, - origin, - mailbox, - existingIsmAddress, - logger, - }); - break; - default: - return this.deploy(params); // TODO: tidy-up update in follow-up PR - } - - return contract; - } - - async deploy(params: { - destination: ChainName; - config: C; - origin?: ChainName; - mailbox?: Address; - }): Promise { - const { destination, config, origin, mailbox } = params; - if (typeof config === 'string') { - // @ts-ignore - return IInterchainSecurityModule__factory.connect( - config, - this.multiProvider.getSignerOrProvider(destination), - ); - } - - const ismType = config.type; - const logger = this.logger.child({ destination, ismType }); - - logger.debug( - `Deploying ${ismType} to ${destination} ${ - origin ? `(for verifying ${origin})` : '' - }`, - ); - - let contract: DeployedIsmType[typeof ismType]; - switch (ismType) { - case IsmType.MESSAGE_ID_MULTISIG: - case IsmType.MERKLE_ROOT_MULTISIG: - contract = await this.deployMultisigIsm(destination, config, logger); - break; - case IsmType.ROUTING: - case IsmType.FALLBACK_ROUTING: - contract = await this.deployRoutingIsm({ - destination, - config, - origin, - mailbox, - logger, - }); - break; - case IsmType.AGGREGATION: - contract = await this.deployAggregationIsm({ - destination, - config, - origin, - mailbox, - logger, - }); - break; - case IsmType.OP_STACK: - assert( - this.deployer, - `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: - assert( - this.deployer, - `HyperlaneDeployer must be set to deploy ${ismType}`, - ); - contract = await this.deployer.deployContractFromFactory( - destination, - new PausableIsm__factory(), - IsmType.PAUSABLE, - [config.owner], - ); - break; - case IsmType.TRUSTED_RELAYER: - assert( - this.deployer, - `HyperlaneDeployer must be set to deploy ${ismType}`, - ); - assert(mailbox, `Mailbox address is required for deploying ${ismType}`); - contract = await this.deployer.deployContractFromFactory( - destination, - new TrustedRelayerIsm__factory(), - IsmType.TRUSTED_RELAYER, - [mailbox, config.relayer], - ); - break; - case IsmType.TEST_ISM: - 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; - default: - throw new Error(`Unsupported ISM type ${ismType}`); - } - - return contract; - } - - protected async deployMultisigIsm( - destination: ChainName, - config: MultisigIsmConfig, - logger: Logger, - ): Promise { - const signer = this.multiProvider.getSigner(destination); - const multisigIsmFactory = - config.type === IsmType.MERKLE_ROOT_MULTISIG - ? this.factories.staticMerkleRootMultisigIsmFactory - : this.factories.staticMessageIdMultisigIsmFactory; - - const address = await this.deployStaticAddressSet( - destination, - multisigIsmFactory, - config.validators, - logger, - config.threshold, - ); - - return IMultisigIsm__factory.connect(address, signer); - } - - protected async updateRoutingIsm(params: { - destination: ChainName; - config: RoutingIsmConfig; - origin?: ChainName; - mailbox?: Address; - existingIsmAddress: Address; - logger: Logger; - }): Promise { - const { destination, config, mailbox, existingIsmAddress, logger } = params; - const overrides = this.multiProvider.getTransactionOverrides(destination); - 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) { - logger.warn( - `Domain ${domain} doesn't have chain metadata provided, skipping ...`, - ); - } - return domainId !== null; - }, - ); - - const safeConfigDomains = Object.keys(config.domains).map((domain) => - this.multiProvider.getDomainId(domain), - ); - - const delta: RoutingIsmDelta = existingIsmAddress - ? await routingModuleDelta( - destination, - existingIsmAddress, - config, - this.multiProvider, - this.factories, - mailbox, - ) - : { - domainsToUnenroll: [], - domainsToEnroll: safeConfigDomains, - }; - - const signer = this.multiProvider.getSigner(destination); - const provider = this.multiProvider.getProvider(destination); - const owner = await DomainRoutingIsm__factory.connect( - existingIsmAddress, - provider, - ).owner(); - const 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 - logger.debug( - `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) { - logger.debug( - `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) { - logger.debug(`Transferring ownership of routing ISM...`); - const tx = await routingIsm.transferOwnership(delta.owner, overrides); - await this.multiProvider.handleTx(destination, tx); - } - } else { - const isms: ChainMap
= {}; - const owner = config.owner; - - 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); - - 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', - ); - } - - // connect to existing ISM - routingIsm = DefaultFallbackRoutingIsm__factory.connect( - existingIsmAddress, - signer, - ); - - // update ISM with config - logger.debug('Initialising fallback routing ISM ...'); - await this.multiProvider.handleTx( - destination, - routingIsm['initialize(address,uint32[],address[])']( - owner, - safeConfigDomains, - submoduleAddresses, - overrides, - ), - ); - } else { - routingIsm = await this.deployDomainRoutingIsm({ - destination, - owner, - safeConfigDomains, - submoduleAddresses, - overrides, - }); - } - } - return routingIsm; - } - - protected async deployRoutingIsm(params: { - destination: ChainName; - config: RoutingIsmConfig; - origin?: ChainName; - mailbox?: Address; - logger: Logger; - }): Promise { - const { destination, config, mailbox, logger } = params; - const overrides = this.multiProvider.getTransactionOverrides(destination); - 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) { - logger.warn( - `Domain ${domain} doesn't have chain metadata provided, skipping ...`, - ); - } - return domainId !== null; - }, - ); - - const safeConfigDomains = Object.keys(config.domains).map((domain) => - this.multiProvider.getDomainId(domain), - ); - - const isms: ChainMap
= {}; - const owner = config.owner; - - 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); - - 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', - ); - } - logger.debug('Deploying fallback routing ISM ...'); - routingIsm = await this.multiProvider.handleDeploy( - destination, - new DefaultFallbackRoutingIsm__factory(), - [mailbox], - ); - } else { - routingIsm = await this.deployDomainRoutingIsm({ - destination, - owner, - safeConfigDomains, - submoduleAddresses, - overrides, - }); - } - - return routingIsm; - } - - protected async deployDomainRoutingIsm(params: { - destination: ChainName; - owner: string; - safeConfigDomains: number[]; - submoduleAddresses: string[]; - overrides: ethers.Overrides; - }): Promise { - const { - destination, - owner, - safeConfigDomains, - submoduleAddresses, - overrides, - } = params; - - // deploying new domain routing ISM - const tx = await this.factories.domainRoutingIsmFactory.deploy( - owner, - safeConfigDomains, - submoduleAddresses, - overrides, - ); - - const receipt = await this.multiProvider.handleTx(destination, tx); - - // TODO: Break this out into a generalized function - const dispatchLogs = receipt.logs - .map((log) => { - try { - return this.factories.domainRoutingIsmFactory.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']; - return DomainRoutingIsm__factory.connect( - moduleAddress, - this.multiProvider.getSigner(destination), - ); - } - - protected async deployAggregationIsm(params: { - destination: ChainName; - config: AggregationIsmConfig; - origin?: ChainName; - mailbox?: Address; - logger: Logger; - }): Promise { - const { destination, config, origin, mailbox } = params; - const signer = this.multiProvider.getSigner(destination); - const staticAggregationIsmFactory = - this.factories.staticAggregationIsmFactory; - const addresses: Address[] = []; - for (const module of config.modules) { - const submodule = await this.deploy({ - destination, - config: module, - origin, - mailbox, - }); - addresses.push(submodule.address); - } - const address = await this.deployStaticAddressSet( - destination, - staticAggregationIsmFactory, - addresses, - params.logger, - config.threshold, - ); - return IAggregationIsm__factory.connect(address, signer); - } - - async deployStaticAddressSet( - chain: ChainName, - factory: StaticThresholdAddressSetFactory | StaticAddressSetFactory, - values: Address[], - logger: Logger, - threshold = values.length, - ): Promise
{ - const sorted = [...values].sort(); - - const address = await factory['getAddress(address[],uint8)']( - sorted, - threshold, - ); - const code = await this.multiProvider.getProvider(chain).getCode(address); - if (code === '0x') { - logger.debug( - `Deploying new ${threshold} of ${values.length} address set to ${chain}`, - ); - const overrides = this.multiProvider.getTransactionOverrides(chain); - const hash = await factory['deploy(address[],uint8)']( - sorted, - threshold, - overrides, - ); - await this.multiProvider.handleTx(chain, hash); - // TODO: add proxy verification artifact? - } else { - logger.debug( - `Recovered ${threshold} of ${values.length} address set on ${chain}: ${address}`, - ); - } - return address; - } -} diff --git a/typescript/sdk/src/ism/EvmIsmModule.hardhat-test.ts b/typescript/sdk/src/ism/EvmIsmModule.hardhat-test.ts new file mode 100644 index 000000000..488854454 --- /dev/null +++ b/typescript/sdk/src/ism/EvmIsmModule.hardhat-test.ts @@ -0,0 +1,434 @@ +/* eslint-disable no-console */ +import assert from 'assert'; +import { expect } from 'chai'; +import { Signer } from 'ethers'; +import hre from 'hardhat'; + +import { FallbackDomainRoutingHook__factory } from '@hyperlane-xyz/core'; +import { Address, eqAddress, normalizeConfig } from '@hyperlane-xyz/utils'; + +import { TestChainName, testChains } from '../consts/testChains.js'; +import { HyperlaneAddresses, HyperlaneContracts } from '../contracts/types.js'; +import { TestCoreDeployer } from '../core/TestCoreDeployer.js'; +import { HyperlaneProxyFactoryDeployer } from '../deploy/HyperlaneProxyFactoryDeployer.js'; +import { ProxyFactoryFactories } from '../deploy/contracts.js'; +import { MultiProvider } from '../providers/MultiProvider.js'; +import { randomAddress, randomInt } from '../test/testUtils.js'; + +import { EvmIsmModule } from './EvmIsmModule.js'; +import { HyperlaneIsmFactory } from './HyperlaneIsmFactory.js'; +import { + AggregationIsmConfig, + IsmConfig, + IsmType, + ModuleType, + MultisigIsmConfig, + RoutingIsmConfig, + TrustedRelayerIsmConfig, +} from './types.js'; + +const randomMultisigIsmConfig = (m: number, n: number): MultisigIsmConfig => { + const emptyArray = new Array(n).fill(0); + const validators = emptyArray.map(() => randomAddress()); + return { + type: IsmType.MERKLE_ROOT_MULTISIG, + validators, + threshold: m, + }; +}; + +function randomModuleType(): ModuleType { + const choices = [ + ModuleType.AGGREGATION, + ModuleType.MERKLE_ROOT_MULTISIG, + ModuleType.ROUTING, + ModuleType.NULL, + ]; + return choices[randomInt(choices.length)]; +} + +const randomIsmConfig = (depth = 0, maxDepth = 2): IsmConfig => { + const moduleType = + depth == maxDepth ? ModuleType.MERKLE_ROOT_MULTISIG : randomModuleType(); + if (moduleType === ModuleType.MERKLE_ROOT_MULTISIG) { + const n = randomInt(5, 1); + return randomMultisigIsmConfig(randomInt(n, 1), n); + } else if (moduleType === ModuleType.ROUTING) { + const config: RoutingIsmConfig = { + type: IsmType.ROUTING, + owner: randomAddress(), + domains: Object.fromEntries( + testChains.map((c) => [c, randomIsmConfig(depth + 1)]), + ), + }; + return config; + } else if (moduleType === ModuleType.AGGREGATION) { + const n = randomInt(5, 1); + const modules = new Array(n) + .fill(0) + .map(() => randomIsmConfig(depth + 1)); + const config: AggregationIsmConfig = { + type: IsmType.AGGREGATION, + threshold: randomInt(n, 1), + modules, + }; + return config; + } else if (moduleType === ModuleType.NULL) { + const config: TrustedRelayerIsmConfig = { + type: IsmType.TRUSTED_RELAYER, + relayer: randomAddress(), + }; + return config; + } else { + throw new Error(`Unsupported ISM type: ${moduleType}`); + } +}; + +describe('EvmIsmModule', async () => { + let multiProvider: MultiProvider; + let ismFactoryDeployer: HyperlaneProxyFactoryDeployer; + let exampleRoutingConfig: RoutingIsmConfig; + let mailboxAddress: Address; + let newMailboxAddress: Address; + let fundingAccount: Signer; + + const chain = TestChainName.test4; + let factoryAddresses: HyperlaneAddresses; + let factoryContracts: HyperlaneContracts; + + beforeEach(async () => { + const [signer, funder] = await hre.ethers.getSigners(); + fundingAccount = funder; + multiProvider = MultiProvider.createTestMultiProvider({ signer }); + + ismFactoryDeployer = new HyperlaneProxyFactoryDeployer(multiProvider); + const contractsMap = await ismFactoryDeployer.deploy( + multiProvider.mapKnownChains(() => ({})), + ); + + // get addresses of factories for the chain + factoryContracts = contractsMap[chain]; + factoryAddresses = Object.keys(factoryContracts).reduce((acc, key) => { + acc[key] = + contractsMap[chain][key as keyof ProxyFactoryFactories].address; + return acc; + }, {} as Record) as HyperlaneAddresses; + + // legacy HyperlaneIsmFactory is required to do a core deploy + const legacyIsmFactory = new HyperlaneIsmFactory( + contractsMap, + multiProvider, + ); + + // mailbox + mailboxAddress = ( + await new TestCoreDeployer(multiProvider, legacyIsmFactory).deployApp() + ).getContracts(chain).mailbox.address; + + // new mailbox + newMailboxAddress = ( + await new TestCoreDeployer(multiProvider, legacyIsmFactory).deployApp() + ).getContracts(chain).mailbox.address; + + // example routing config + exampleRoutingConfig = { + type: IsmType.ROUTING, + owner: await multiProvider.getSignerAddress(chain), + domains: Object.fromEntries( + testChains + .filter((c) => c !== TestChainName.test4) + .map((c) => [c, randomMultisigIsmConfig(3, 5)]), + ), + }; + }); + + // Helper method for create a new multiprovider with an impersonated account + async function impersonateAccount(account: Address): Promise { + await hre.ethers.provider.send('hardhat_impersonateAccount', [account]); + await fundingAccount.sendTransaction({ + to: account, + value: hre.ethers.utils.parseEther('1.0'), + }); + return MultiProvider.createTestMultiProvider({ + signer: hre.ethers.provider.getSigner(account), + }); + } + + // Helper method to expect exactly N updates to be applied + async function expectTxsAndUpdate( + ism: EvmIsmModule, + config: IsmConfig, + n: number, + ) { + const txs = await ism.update(config); + expect(txs.length).to.equal(n); + + for (const tx of txs) { + await multiProvider.sendTransaction(chain, tx); + } + } + + // ism module and config for testing + let testIsm: EvmIsmModule; + let testConfig: IsmConfig; + + // expect that the ISM matches the config after all tests + afterEach(async () => { + const normalizedDerivedConfig = normalizeConfig(await testIsm.read()); + const normalizedConfig = normalizeConfig(testConfig); + assert.deepStrictEqual(normalizedDerivedConfig, normalizedConfig); + }); + + // create a new ISM and verify that it matches the config + async function createIsm( + config: IsmConfig, + ): Promise<{ ism: EvmIsmModule; initialIsmAddress: Address }> { + const ism = await EvmIsmModule.create({ + chain, + config, + deployer: ismFactoryDeployer, + factories: factoryAddresses, + mailbox: mailboxAddress, + multiProvider, + }); + testIsm = ism; + testConfig = config; + return { ism, initialIsmAddress: ism.serialize().deployedIsm }; + } + + describe('create', async () => { + it('deploys a simple ism', async () => { + const config = randomMultisigIsmConfig(3, 5); + await createIsm(config); + }); + + it('deploys a trusted relayer ism', async () => { + const relayer = randomAddress(); + const config: TrustedRelayerIsmConfig = { + type: IsmType.TRUSTED_RELAYER, + relayer, + }; + await createIsm(config); + }); + + 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; + await createIsm(exampleRoutingConfig); + }); + } + + for (let i = 0; i < 16; i++) { + it(`deploys a random ism config #${i}`, async () => { + const config = randomIsmConfig(); + await createIsm(config); + }); + } + }); + + describe('update', async () => { + for (const type of [IsmType.ROUTING, IsmType.FALLBACK_ROUTING]) { + beforeEach(() => { + exampleRoutingConfig.type = type as + | IsmType.ROUTING + | IsmType.FALLBACK_ROUTING; + }); + + it(`should skip deployment with warning if no chain metadata configured ${type}`, async () => { + // create a new ISM + const { ism } = await createIsm(exampleRoutingConfig); + + // add config for a domain the multiprovider doesn't have + exampleRoutingConfig.domains['test5'] = { + type: IsmType.MESSAGE_ID_MULTISIG, + threshold: 1, + validators: [randomAddress()], + }; + + // expect 0 txs, as adding test5 domain is no-op + await expectTxsAndUpdate(ism, exampleRoutingConfig, 0); + }); + + it(`update route in an existing ${type}`, async () => { + // create a new ISM + const { ism, initialIsmAddress } = await createIsm( + exampleRoutingConfig, + ); + + // changing the type of a domain should enroll the domain + ( + exampleRoutingConfig.domains[TestChainName.test2] as MultisigIsmConfig + ).type = IsmType.MESSAGE_ID_MULTISIG; + + // expect 1 tx to enroll test2 domain + await expectTxsAndUpdate(ism, exampleRoutingConfig, 1); + + // check that the ISM address is the same + expect(eqAddress(initialIsmAddress, ism.serialize().deployedIsm)).to.be + .true; + }); + + it(`deletes route in an existing ${type}`, async () => { + // create a new ISM + const { ism, initialIsmAddress } = await createIsm( + exampleRoutingConfig, + ); + + // deleting the domain should unenroll the domain + delete exampleRoutingConfig.domains[TestChainName.test3]; + + // expect 1 tx to unenroll test3 domain + await expectTxsAndUpdate(ism, exampleRoutingConfig, 1); + + // expect the ISM address to be the same + expect(eqAddress(initialIsmAddress, ism.serialize().deployedIsm)).to.be + .true; + }); + + it(`deletes route in an existing ${type} even if not in multiprovider`, async () => { + // create a new ISM + const { ism } = await createIsm(exampleRoutingConfig); + + // keep track of the domains before deleting + const numDomainsBefore = Object.keys( + ((await ism.read()) as RoutingIsmConfig).domains, + ).length; + + // deleting the domain and removing from multiprovider should unenroll the domain + delete exampleRoutingConfig.domains[TestChainName.test3]; + multiProvider = multiProvider.intersect( + // remove test3 from multiprovider + testChains.filter((c) => c !== TestChainName.test3), + ).result; + + // expect 1 tx to unenroll test3 domain + await expectTxsAndUpdate(ism, exampleRoutingConfig, 1); + + // domains should have decreased by 1 + const numDomainsAfter = Object.keys( + ((await ism.read()) as RoutingIsmConfig).domains, + ).length; + console.log(numDomainsBefore, numDomainsAfter); + expect(numDomainsBefore - 1).to.equal(numDomainsAfter); + }); + + it(`updates owner in an existing ${type}`, async () => { + // create a new ISM + const { ism, initialIsmAddress } = await createIsm( + exampleRoutingConfig, + ); + + // change the config owner + exampleRoutingConfig.owner = randomAddress(); + + // expect 1 tx to transfer ownership + await expectTxsAndUpdate(ism, exampleRoutingConfig, 1); + + // expect the ISM address to be the same + expect(eqAddress(initialIsmAddress, ism.serialize().deployedIsm)).to.be + .true; + }); + + it(`no changes to an existing ${type} means no redeployment or updates`, async () => { + // create a new ISM + const { ism, initialIsmAddress } = await createIsm( + exampleRoutingConfig, + ); + + // expect 0 updates + await expectTxsAndUpdate(ism, exampleRoutingConfig, 0); + + // expect the ISM address to be the same + expect(eqAddress(initialIsmAddress, ism.serialize().deployedIsm)).to.be + .true; + }); + + it(`update owner in an existing ${type} not owned by deployer`, async () => { + // ISM owner is not the deployer + exampleRoutingConfig.owner = randomAddress(); + const originalOwner = exampleRoutingConfig.owner; + + // create a new ISM + const { ism, initialIsmAddress } = await createIsm( + exampleRoutingConfig, + ); + + // update the config owner and impersonate the original owner + exampleRoutingConfig.owner = randomAddress(); + multiProvider = await impersonateAccount(originalOwner); + + // expect 1 tx to transfer ownership + await expectTxsAndUpdate(ism, exampleRoutingConfig, 1); + + // expect the ISM address to be unchanged + expect(eqAddress(initialIsmAddress, ism.serialize().deployedIsm)).to.be + .true; + }); + + it(`update validators in an existing ${type}`, async () => { + // create a new ISM + const { ism, initialIsmAddress } = await createIsm( + exampleRoutingConfig, + ); + + // update the validators for a domain + ( + exampleRoutingConfig.domains[TestChainName.test2] as MultisigIsmConfig + ).validators = [randomAddress(), randomAddress()]; + + // expect 1 tx to update validator set for test2 domain + await expectTxsAndUpdate(ism, exampleRoutingConfig, 1); + + // expect the ISM address to be the same + expect(eqAddress(initialIsmAddress, ism.serialize().deployedIsm)).to.be + .true; + }); + + it(`update threshold in an existing ${type}`, async () => { + // create a new ISM + const { ism, initialIsmAddress } = await createIsm( + exampleRoutingConfig, + ); + + // update the threshold for a domain + ( + exampleRoutingConfig.domains[TestChainName.test2] as MultisigIsmConfig + ).threshold = 2; + + // expect 1 tx to update threshold for test2 domain + await expectTxsAndUpdate(ism, exampleRoutingConfig, 1); + + // expect the ISM address to be the same + expect(eqAddress(initialIsmAddress, ism.serialize().deployedIsm)).to.be + .true; + }); + } + + it(`redeploy same config if the mailbox address changes for defaultFallbackRoutingIsm`, async () => { + exampleRoutingConfig.type = IsmType.FALLBACK_ROUTING; + + // create a new ISM + const { ism, initialIsmAddress } = await createIsm(exampleRoutingConfig); + + // point to new mailbox + ism.setNewMailbox(newMailboxAddress); + + // expect a new ISM to be deployed, so no in-place updates to return + await expectTxsAndUpdate(ism, exampleRoutingConfig, 0); + + // expect the ISM address to be different + expect(eqAddress(initialIsmAddress, ism.serialize().deployedIsm)).to.be + .false; + + // expect that the ISM is configured with the new mailbox + const onchainIsm = FallbackDomainRoutingHook__factory.connect( + ism.serialize().deployedIsm, + multiProvider.getSigner(chain), + ); + const onchainMailbox = await onchainIsm['mailbox()'](); + expect(eqAddress(onchainMailbox, newMailboxAddress)).to.be.true; + }); + }); +}); diff --git a/typescript/sdk/src/ism/EvmIsmModule.ts b/typescript/sdk/src/ism/EvmIsmModule.ts index aa1b41d54..5664687ed 100644 --- a/typescript/sdk/src/ism/EvmIsmModule.ts +++ b/typescript/sdk/src/ism/EvmIsmModule.ts @@ -1,59 +1,656 @@ -import { Address, ProtocolType, rootLogger } from '@hyperlane-xyz/utils'; +import { ethers } from 'ethers'; +import { Logger } from 'pino'; -import { HyperlaneAddresses } from '../contracts/types.js'; +import { + DefaultFallbackRoutingIsm__factory, + DomainRoutingIsm, + DomainRoutingIsmFactory__factory, + DomainRoutingIsm__factory, + IAggregationIsm, + IAggregationIsm__factory, + IInterchainSecurityModule__factory, + IMultisigIsm, + IMultisigIsm__factory, + IRoutingIsm, + MailboxClient__factory, + OPStackIsm__factory, + Ownable__factory, + PausableIsm__factory, + StaticAddressSetFactory, + StaticThresholdAddressSetFactory, + TestIsm__factory, + TrustedRelayerIsm__factory, +} from '@hyperlane-xyz/core'; +import { + Address, + ProtocolType, + assert, + configDeepEquals, + eqAddress, + normalizeConfig, + objFilter, + rootLogger, +} from '@hyperlane-xyz/utils'; +import { Domain } from '@hyperlane-xyz/utils'; + +import { attachAndConnectContracts } from '../contracts/contracts.js'; +import { HyperlaneAddresses, HyperlaneContracts } from '../contracts/types.js'; import { HyperlaneModule, - HyperlaneModuleArgs, + HyperlaneModuleParams, } from '../core/AbstractHyperlaneModule.js'; import { HyperlaneDeployer } from '../deploy/HyperlaneDeployer.js'; -import { ProxyFactoryFactories } from '../deploy/contracts.js'; +import { + ProxyFactoryFactories, + proxyFactoryFactories, +} from '../deploy/contracts.js'; import { MultiProvider } from '../providers/MultiProvider.js'; -import { EthersV5Transaction } from '../providers/ProviderType.js'; -import { ChainNameOrId } from '../types.js'; +import { AnnotatedEV5Transaction } from '../providers/ProviderType.js'; +import { ChainName, ChainNameOrId } from '../types.js'; +import { findMatchingLogEvents } from '../utils/logUtils.js'; import { EvmIsmReader } from './EvmIsmReader.js'; -import { IsmConfig } from './types.js'; +import { + AggregationIsmConfig, + DeployedIsm, + IsmConfig, + IsmType, + MUTABLE_ISM_TYPE, + MultisigIsmConfig, + RoutingIsmConfig, +} from './types.js'; +import { calculateDomainRoutingDelta } from './utils.js'; + +type IsmModuleAddresses = { + deployedIsm: Address; + mailbox: Address; +}; export class EvmIsmModule extends HyperlaneModule< ProtocolType.Ethereum, IsmConfig, - HyperlaneAddresses & { - deployedIsm: Address; - } + HyperlaneAddresses & IsmModuleAddresses > { - protected logger = rootLogger.child({ module: 'EvmIsmModule' }); - protected reader: EvmIsmReader; + protected readonly logger = rootLogger.child({ module: 'EvmIsmModule' }); + protected readonly reader: EvmIsmReader; + protected readonly factories: HyperlaneContracts; + + // Adding these to reduce how often we need to grab from MultiProvider. + public readonly chain: ChainName; + // We use domainId here because MultiProvider.getDomainId() will always + // return a number, and EVM the domainId and chainId are the same. + public readonly domainId: Domain; protected constructor( protected readonly multiProvider: MultiProvider, protected readonly deployer: HyperlaneDeployer, - args: HyperlaneModuleArgs< + params: HyperlaneModuleParams< IsmConfig, - HyperlaneAddresses & { - deployedIsm: Address; - } + HyperlaneAddresses & IsmModuleAddresses >, ) { - super(args); - this.reader = new EvmIsmReader(multiProvider, args.chain); + super(params); + + this.reader = new EvmIsmReader(multiProvider, params.chain); + const { mailbox: _, deployedIsm: __, ...addresses } = params.addresses; + this.factories = attachAndConnectContracts( + addresses, + proxyFactoryFactories, + multiProvider.getSigner(params.chain), + ); + + this.chain = this.multiProvider.getChainName(this.args.chain); + this.domainId = this.multiProvider.getDomainId(this.chain); } public async read(): Promise { - return this.reader.deriveIsmConfig(this.args.addresses.deployedIsm); + return typeof this.args.config === 'string' + ? this.args.addresses.deployedIsm + : this.reader.deriveIsmConfig(this.args.addresses.deployedIsm); } - public async update(_config: IsmConfig): Promise { - throw new Error('Method not implemented.'); + // whoever calls update() needs to ensure that targetConfig has a valid owner + public async update( + targetConfig: IsmConfig, + ): Promise { + // save current config for comparison + // normalize the config to ensure it's in a consistent format for comparison + const currentConfig = normalizeConfig(await this.read()); + + // Update the config + this.args.config = targetConfig; + + // moduleMatchesConfig expects any domain filtering to have been done already + if ( + typeof targetConfig !== 'string' && + (targetConfig.type === IsmType.ROUTING || + targetConfig.type === IsmType.FALLBACK_ROUTING) + ) { + // filter for known domains + const { availableDomains } = this.filterRoutingIsmDomains({ + config: targetConfig, + }); + targetConfig.domains = availableDomains; + } + + // If configs match, no updates needed + if (configDeepEquals(currentConfig, targetConfig)) { + return []; + } + + // Else, we have to figure out what an update for this ISM entails + + // If target config is a custom ISM, just update the address + // if config -> custom ISM, update address + // if custom ISM -> custom ISM, update address + if (typeof targetConfig === 'string') { + // TODO: https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/3773 + this.args.addresses.deployedIsm = targetConfig; + return []; + } + + // Check if we need to deploy a new ISM + if ( + // if custom ISM -> config, do a new deploy + typeof currentConfig === 'string' || + // if config -> config, AND types are different, do a new deploy + currentConfig.type !== targetConfig.type || + // if it is not a mutable ISM, do a new deploy + !MUTABLE_ISM_TYPE.includes(targetConfig.type) + ) { + const contract = await this.deploy({ + config: targetConfig, + }); + + this.args.addresses.deployedIsm = contract.address; + return []; + } + + // At this point, only the 3 ownable/mutable ISM types should remain: PAUSABLE, ROUTING, FALLBACK_ROUTING + if ( + targetConfig.type !== IsmType.PAUSABLE && + targetConfig.type !== IsmType.ROUTING && + targetConfig.type !== IsmType.FALLBACK_ROUTING + ) { + throw new Error(`Unsupported ISM type ${targetConfig.type}`); + } + + const logger = this.logger.child({ + destination: this.chain, + ismType: targetConfig.type, + }); + const provider = this.multiProvider.getProvider(this.chain); + + logger.debug(`Updating ${targetConfig.type} on ${this.chain}`); + + // if it's a fallback routing ISM, do a mailbox diff check and deploy a new ISM if needed + if (targetConfig.type === IsmType.FALLBACK_ROUTING) { + // can only retreive mailbox address if current ISM type is also Fallback Routing + const mailboxAddress = + currentConfig.type === IsmType.FALLBACK_ROUTING + ? await MailboxClient__factory.connect( + this.args.addresses.deployedIsm, + provider, + ).mailbox() + : ''; // empty string to force a mailbox diff + + // if mailbox delta, deploy new routing ISM before updating + // this will always be the case if the current ISM is not a fallback routing ISM + if (!eqAddress(mailboxAddress, this.args.addresses.mailbox)) { + const newIsm = await this.deployRoutingIsm({ + config: targetConfig, + logger, + }); + + this.args.addresses.deployedIsm = newIsm.address; + } + } + + // if it's either of the routing ISMs, update their submodules + let updateTxs: AnnotatedEV5Transaction[] = []; + if ( + targetConfig.type === IsmType.ROUTING || + targetConfig.type === IsmType.FALLBACK_ROUTING + ) { + updateTxs = await this.updateRoutingIsm({ + current: currentConfig as RoutingIsmConfig, + target: targetConfig, + logger, + }); + } + + // Lastly, check if the resolved owner is different from the current owner + const owner = await Ownable__factory.connect( + this.args.addresses.deployedIsm, + provider, + ).owner(); + + // Return an ownership transfer transaction if required + if (!eqAddress(targetConfig.owner, owner)) { + updateTxs.push({ + annotation: 'Transferring ownership of ownable ISM...', + chainId: this.domainId, + to: this.args.addresses.deployedIsm, + data: Ownable__factory.createInterface().encodeFunctionData( + 'transferOwnership(address)', + [targetConfig.owner], + ), + }); + } + + return updateTxs; } // manually write static create function - public static async create(_params: { + public static async create(params: { chain: ChainNameOrId; config: IsmConfig; deployer: HyperlaneDeployer; factories: HyperlaneAddresses; + mailbox: Address; multiProvider: MultiProvider; }): Promise { - throw new Error('Method not implemented.'); + const { chain, config, deployer, factories, mailbox, multiProvider } = + params; + + // instantiate new EvmIsmModule + const module = new EvmIsmModule(multiProvider, deployer, { + addresses: { + ...factories, + mailbox, + deployedIsm: ethers.constants.AddressZero, + }, + chain, + config, + }); + + // deploy ISM and assign address to module + const deployedIsm = await module.deploy({ config }); + module.args.addresses.deployedIsm = deployedIsm.address; + + return module; + } + + protected async updateRoutingIsm({ + current, + target, + logger, + }: { + current: RoutingIsmConfig; + target: RoutingIsmConfig; + logger: Logger; + }): Promise { + const routingIsmInterface = DomainRoutingIsm__factory.createInterface(); + const updateTxs = []; + + const { domainsToEnroll, domainsToUnenroll } = calculateDomainRoutingDelta( + current, + target, + ); + + // Enroll domains + for (const origin of domainsToEnroll) { + logger.debug( + `Reconfiguring preexisting routing ISM for origin ${origin}...`, + ); + const ism = await this.deploy({ + config: target.domains[origin], + }); + + const domainId = this.multiProvider.getDomainId(origin); + updateTxs.push({ + annotation: `Setting new ISM for origin ${origin}...`, + chainId: this.domainId, + to: this.args.addresses.deployedIsm, + data: routingIsmInterface.encodeFunctionData('set(uint32,address)', [ + domainId, + ism.address, + ]), + }); + } + + // Unenroll domains + for (const origin of domainsToUnenroll) { + const domainId = this.multiProvider.getDomainId(origin); + updateTxs.push({ + annotation: `Unenrolling originDomain ${domainId} from preexisting routing ISM at ${this.args.addresses.deployedIsm}...`, + chainId: this.domainId, + to: this.args.addresses.deployedIsm, + data: routingIsmInterface.encodeFunctionData('remove(uint32)', [ + domainId, + ]), + }); + } + + return updateTxs; + } + + protected async deploy({ + config, + }: { + config: C; + }): Promise { + // If it's a custom ISM, just return a base ISM + if (typeof config === 'string') { + // TODO: https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/3773 + // we can remove the ts-ignore once we have a proper type for custom ISMs + // @ts-ignore + return IInterchainSecurityModule__factory.connect( + config, + this.multiProvider.getSignerOrProvider(this.args.chain), + ); + } + + const ismType = config.type; + const logger = rootLogger.child({ chainName: this.chain, ismType }); + + logger.debug(`Deploying ${ismType} to ${this.args.chain}`); + + switch (ismType) { + case IsmType.MESSAGE_ID_MULTISIG: + case IsmType.MERKLE_ROOT_MULTISIG: + return this.deployMultisigIsm({ + config, + logger, + }); + + case IsmType.ROUTING: + case IsmType.FALLBACK_ROUTING: + return this.deployRoutingIsm({ + config, + logger, + }); + + case IsmType.AGGREGATION: + return this.deployAggregationIsm({ + config, + logger, + }); + + case IsmType.OP_STACK: + return this.deployer.deployContractFromFactory( + this.chain, + new OPStackIsm__factory(), + IsmType.OP_STACK, + [config.nativeBridge], + ); + + case IsmType.PAUSABLE: + return this.deployer.deployContractFromFactory( + this.chain, + new PausableIsm__factory(), + IsmType.PAUSABLE, + [config.owner], + ); + + case IsmType.TRUSTED_RELAYER: + assert( + this.args.addresses.mailbox, + `Mailbox address is required for deploying ${ismType}`, + ); + return this.deployer.deployContractFromFactory( + this.chain, + new TrustedRelayerIsm__factory(), + IsmType.TRUSTED_RELAYER, + [this.args.addresses.mailbox, config.relayer], + ); + + case IsmType.TEST_ISM: + return this.deployer.deployContractFromFactory( + this.chain, + new TestIsm__factory(), + IsmType.TEST_ISM, + [], + ); + + default: + throw new Error(`Unsupported ISM type ${ismType}`); + } + } + + protected async deployMultisigIsm({ + config, + logger, + }: { + config: MultisigIsmConfig; + logger: Logger; + }): Promise { + const signer = this.multiProvider.getSigner(this.chain); + const factoryName = + config.type === IsmType.MERKLE_ROOT_MULTISIG + ? 'staticMerkleRootMultisigIsmFactory' + : 'staticMessageIdMultisigIsmFactory'; + + const address = await EvmIsmModule.deployStaticAddressSet({ + chain: this.chain, + factory: this.factories[factoryName], + values: config.validators, + logger, + threshold: config.threshold, + multiProvider: this.multiProvider, + }); + + return IMultisigIsm__factory.connect(address, signer); + } + + protected async deployRoutingIsm({ + config, + logger, + }: { + config: RoutingIsmConfig; + logger: Logger; + }): Promise { + // filter out domains which are not part of the multiprovider + const { availableDomains, availableDomainIds } = + this.filterRoutingIsmDomains({ + config, + }); + config.domains = availableDomains; + + // deploy the submodules first + const submoduleAddresses: Address[] = await Promise.all( + Object.keys(config.domains).map(async (origin) => { + const { address } = await this.deploy({ + config: config.domains[origin], + }); + return address; + }), + ); + + if (config.type === IsmType.FALLBACK_ROUTING) { + // deploy the fallback routing ISM + logger.debug('Deploying fallback routing ISM ...'); + const ism = await this.multiProvider.handleDeploy( + this.chain, + new DefaultFallbackRoutingIsm__factory(), + [this.args.addresses.mailbox], + ); + + // initialize the fallback routing ISM + logger.debug('Initializing fallback routing ISM ...'); + await ism['initialize(address,uint32[],address[])']( + config.owner, + availableDomainIds, + submoduleAddresses, + ); + + // return the fallback routing ISM + return ism; + } + + // then deploy the domain routing ISM + logger.debug('Deploying domain routing ISM ...'); + return this.deployDomainRoutingIsm({ + owner: config.owner, + domainIds: availableDomainIds, + submoduleAddresses, + }); + } + + protected async deployDomainRoutingIsm({ + owner, + domainIds, + submoduleAddresses, + }: { + owner: string; + domainIds: number[]; + submoduleAddresses: string[]; + }): Promise { + const overrides = this.multiProvider.getTransactionOverrides( + this.args.chain, + ); + + const signer = this.multiProvider.getSigner(this.args.chain); + const domainRoutingIsmFactory = DomainRoutingIsmFactory__factory.connect( + this.args.addresses.domainRoutingIsmFactory, + signer, + ); + + // deploying new domain routing ISM + const tx = await domainRoutingIsmFactory.deploy( + owner, + domainIds, + submoduleAddresses, + overrides, + ); + + const receipt = await this.multiProvider.handleTx(this.args.chain, tx); + const dispatchLogs = findMatchingLogEvents( + receipt.logs, + domainRoutingIsmFactory.interface, + 'ModuleDeployed', + ); + + if (dispatchLogs.length === 0) { + throw new Error('No ModuleDeployed event found'); + } + + const moduleAddress = dispatchLogs[0].args['module']; + return DomainRoutingIsm__factory.connect(moduleAddress, signer); + } + + protected async deployAggregationIsm({ + config, + logger, + }: { + config: AggregationIsmConfig; + logger: Logger; + }): Promise { + const addresses: Address[] = await Promise.all( + config.modules.map(async (module) => { + const submodule = await this.deploy({ config: module }); + return submodule.address; + }), + ); + + const factoryName = 'staticAggregationIsmFactory'; + const address = await EvmIsmModule.deployStaticAddressSet({ + chain: this.chain, + factory: this.factories[factoryName], + values: addresses, + logger: logger, + threshold: config.threshold, + multiProvider: this.multiProvider, + }); + + const signer = this.multiProvider.getSigner(this.args.chain); + return IAggregationIsm__factory.connect(address, signer); + } + + // Updates the mailbox address if it is different from the current one. + // Logs changes and updates the internal state of the module. + public setNewMailbox(newMailboxAddress: Address): void { + const currentMailboxAddress = this.args.addresses.mailbox; + + if (currentMailboxAddress === newMailboxAddress) { + this.logger.debug( + `Mailbox address is already set to ${newMailboxAddress}`, + ); + return; + } + + this.logger.debug( + `Setting new mailbox address from ${currentMailboxAddress} to ${newMailboxAddress}`, + ); + + // Update the mailbox address in the arguments + this.args.addresses.mailbox = newMailboxAddress; + } + + // Public so it can be reused by the hook module. + // Caller of this function is responsible for verifying the contract + // because they know exactly which factory is being called. + public static async deployStaticAddressSet({ + chain, + factory, + values, + logger, + threshold = values.length, + multiProvider, + }: { + chain: ChainName; + factory: StaticThresholdAddressSetFactory | StaticAddressSetFactory; + values: Address[]; + logger: Logger; + threshold?: number; + multiProvider: MultiProvider; + }): Promise
{ + const address = await factory['getAddress(address[],uint8)']( + values, + threshold, + ); + const code = await multiProvider.getProvider(chain).getCode(address); + if (code === '0x') { + logger.debug( + `Deploying new ${threshold} of ${values.length} address set to ${chain}`, + ); + const overrides = multiProvider.getTransactionOverrides(chain); + const hash = await factory['deploy(address[],uint8)']( + values, + threshold, + overrides, + ); + await multiProvider.handleTx(chain, hash); + } else { + logger.debug( + `Recovered ${threshold} of ${values.length} address set on ${chain}: ${address}`, + ); + } + + // TODO: figure out how to get the constructor arguments for manual deploy TXs + // const verificationInput = buildVerificationInput( + // NAME, + // ADDRESS, + // CONSTRUCTOR_ARGS, + // ); + // await this.deployer.verifyContract( + // this.chainName, + // verificationInput, + // logger, + // ); + + return address; + } + + // filtering out domains which are not part of the multiprovider + private filterRoutingIsmDomains({ config }: { config: RoutingIsmConfig }) { + const availableDomainIds: number[] = []; + const availableDomains = objFilter( + config.domains, + (domain, _): _ is IsmConfig => { + const domainId = this.multiProvider.tryGetDomainId(domain); + if (domainId === null) { + this.logger.warn( + `Domain ${domain} doesn't have chain metadata provided, skipping ...`, + ); + return false; + } + + availableDomainIds.push(domainId); + return true; + }, + ); + + return { availableDomains, availableDomainIds }; } } diff --git a/typescript/sdk/src/ism/HyperlaneIsmFactory.hardhat-test.ts b/typescript/sdk/src/ism/HyperlaneIsmFactory.hardhat-test.ts index f535277e4..180f49371 100644 --- a/typescript/sdk/src/ism/HyperlaneIsmFactory.hardhat-test.ts +++ b/typescript/sdk/src/ism/HyperlaneIsmFactory.hardhat-test.ts @@ -126,7 +126,7 @@ describe('HyperlaneIsmFactory', async () => { owner: await multiProvider.getSignerAddress(chain), domains: Object.fromEntries( testChains - .filter((c) => c !== TestChainName.test1) + .filter((c) => c !== TestChainName.test1 && c !== TestChainName.test4) .map((c) => [c, randomMultisigIsmConfig(3, 5)]), ), }; diff --git a/typescript/sdk/src/ism/types.ts b/typescript/sdk/src/ism/types.ts index c1c27781c..b1dc1fe64 100644 --- a/typescript/sdk/src/ism/types.ts +++ b/typescript/sdk/src/ism/types.ts @@ -50,6 +50,13 @@ export enum IsmType { TRUSTED_RELAYER = 'trustedRelayerIsm', } +// ISM types that can be updated in-place +export const MUTABLE_ISM_TYPE = [ + IsmType.ROUTING, + IsmType.FALLBACK_ROUTING, + IsmType.PAUSABLE, +]; + // mapping between the two enums export function ismTypeToModuleType(ismType: IsmType): ModuleType { switch (ismType) { diff --git a/typescript/sdk/src/ism/utils.ts b/typescript/sdk/src/ism/utils.ts index 8f9cac53a..6b1574925 100644 --- a/typescript/sdk/src/ism/utils.ts +++ b/typescript/sdk/src/ism/utils.ts @@ -14,6 +14,7 @@ import { } from '@hyperlane-xyz/core'; import { Address, + configDeepEquals, eqAddress, formatMessage, normalizeAddress, @@ -37,6 +38,46 @@ import { const logger = rootLogger.child({ module: 'IsmUtils' }); +// Determines the domains to enroll and unenroll to update the current ISM config +// to match the target ISM config. +export function calculateDomainRoutingDelta( + current: RoutingIsmConfig, + target: RoutingIsmConfig, +): { domainsToEnroll: ChainName[]; domainsToUnenroll: ChainName[] } { + const domainsToEnroll = []; + for (const origin of Object.keys(target.domains)) { + if (!current.domains[origin]) { + domainsToEnroll.push(origin); + } else { + const subModuleMatches = configDeepEquals( + current.domains[origin], + target.domains[origin], + ); + if (!subModuleMatches) domainsToEnroll.push(origin); + } + } + + const domainsToUnenroll = Object.keys(current.domains).reduce( + (acc, origin) => { + if (!Object.keys(target.domains).includes(origin)) { + acc.push(origin); + } + return acc; + }, + [] as ChainName[], + ); + + return { + domainsToEnroll, + domainsToUnenroll, + }; +} + +/* + * The following functions are considered legacy and are deprecated. DO NOT USE. + * ----------------------------------------------------------------------------- + */ + // 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 diff --git a/typescript/sdk/src/providers/MultiProvider.ts b/typescript/sdk/src/providers/MultiProvider.ts index 0df4567dc..fb8a36b19 100644 --- a/typescript/sdk/src/providers/MultiProvider.ts +++ b/typescript/sdk/src/providers/MultiProvider.ts @@ -16,6 +16,7 @@ import { ChainMetadataManager } from '../metadata/ChainMetadataManager.js'; import { ChainMetadata } from '../metadata/chainMetadataTypes.js'; import { ChainMap, ChainName, ChainNameOrId } from '../types.js'; +import { AnnotatedEV5Transaction } from './ProviderType.js'; import { ProviderBuilderFn, defaultProviderBuilder, @@ -384,9 +385,13 @@ export class MultiProvider extends ChainMetadataManager { */ async sendTransaction( chainNameOrId: ChainNameOrId, - tx: PopulatedTransaction | Promise, + txProm: AnnotatedEV5Transaction | Promise, ): Promise { - const txReq = await this.prepareTx(chainNameOrId, await tx); + const { annotation, ...tx } = await txProm; + if (annotation) { + this.logger.info(annotation); + } + const txReq = await this.prepareTx(chainNameOrId, tx); const signer = this.getSigner(chainNameOrId); const response = await signer.sendTransaction(txReq); this.logger.info(`Sent tx ${response.hash}`); diff --git a/typescript/sdk/src/providers/ProviderType.ts b/typescript/sdk/src/providers/ProviderType.ts index fa656766b..d7d84f152 100644 --- a/typescript/sdk/src/providers/ProviderType.ts +++ b/typescript/sdk/src/providers/ProviderType.ts @@ -15,7 +15,6 @@ import type { providers as EV5Providers, PopulatedTransaction as EV5Transaction, } from 'ethers'; -// import type { Contract as Ev6Contract, Provider as Ev6Provider } from 'ethers6'; import type { GetContractReturnType, PublicClient, @@ -27,7 +26,6 @@ import { ProtocolType } from '@hyperlane-xyz/utils'; export enum ProviderType { EthersV5 = 'ethers-v5', - // EthersV6 = 'ethers-v6', Disabled for now to simplify build tooling Viem = 'viem', SolanaWeb3 = 'solana-web3', CosmJs = 'cosmjs', @@ -103,11 +101,6 @@ export interface EthersV5Provider provider: EV5Providers.Provider; } -// export interface EthersV6Provider extends TypedProviderBase { -// type: ProviderType.EthersV6; -// provider: Ev6Provider; -// } - export interface ViemProvider extends TypedProviderBase { type: ProviderType.Viem; provider: PublicClient; @@ -152,11 +145,6 @@ export interface EthersV5Contract extends TypedContractBase { contract: EV5Contract; } -// export interface EthersV6Contract extends TypedContractBase { -// type: ProviderType.EthersV6; -// contract: Ev6Contract; -// } - export interface ViemContract extends TypedContractBase { type: ProviderType.Viem; contract: GetContractReturnType; @@ -203,10 +191,9 @@ export interface EthersV5Transaction transaction: EV5Transaction; } -// export interface EthersV6Transaction extends TypedTransactionBase { -// type: ProviderType.EthersV6; -// contract: Ev6Transaction; -// } +export interface AnnotatedEV5Transaction extends EV5Transaction { + annotation?: string; +} export interface ViemTransaction extends TypedTransactionBase { type: ProviderType.Viem; diff --git a/typescript/sdk/src/test/testUtils.ts b/typescript/sdk/src/test/testUtils.ts index 84fc8934c..7e0356f40 100644 --- a/typescript/sdk/src/test/testUtils.ts +++ b/typescript/sdk/src/test/testUtils.ts @@ -12,6 +12,10 @@ import { IsmType } from '../ism/types.js'; import { RouterConfig } from '../router/types.js'; import { ChainMap, ChainName } from '../types.js'; +export function randomInt(max: number, min = 0): number { + return Math.floor(Math.random() * (max - min)) + min; +} + export function randomAddress(): Address { return ethers.utils.hexlify(ethers.utils.randomBytes(20)); } diff --git a/typescript/sdk/src/utils/logUtils.ts b/typescript/sdk/src/utils/logUtils.ts new file mode 100644 index 000000000..5e2eeb366 --- /dev/null +++ b/typescript/sdk/src/utils/logUtils.ts @@ -0,0 +1,21 @@ +import { ethers } from 'ethers'; +import { Log } from 'viem'; + +export function findMatchingLogEvents( + logs: (ethers.providers.Log | Log)[], + iface: ethers.utils.Interface, + eventName: string, +): ethers.utils.LogDescription[] { + return logs + .map((log) => { + try { + return iface.parseLog(log); + } catch (e) { + return undefined; + } + }) + .filter( + (log): log is ethers.utils.LogDescription => + !!log && log.name === eventName, + ); +} diff --git a/typescript/utils/src/index.ts b/typescript/utils/src/index.ts index fdaa20476..d12f41936 100644 --- a/typescript/utils/src/index.ts +++ b/typescript/utils/src/index.ts @@ -111,6 +111,8 @@ export { pick, promiseObjAll, stringifyObject, + normalizeConfig, + configDeepEquals, } from './objects.js'; export { difference, setEquality, symmetricDifference } from './sets.js'; export { diff --git a/typescript/utils/src/objects.ts b/typescript/utils/src/objects.ts index 131ab1f4b..531997f1e 100644 --- a/typescript/utils/src/objects.ts +++ b/typescript/utils/src/objects.ts @@ -1,6 +1,7 @@ +import { deepStrictEqual } from 'node:assert/strict'; import { stringify as yamlStringify } from 'yaml'; -import { ethersBigNumberSerializer } from './logging.js'; +import { ethersBigNumberSerializer, rootLogger } from './logging.js'; import { assert } from './validation.js'; export function isObject(item: any) { @@ -154,3 +155,32 @@ export function stringifyObject( } return yamlStringify(JSON.parse(json), null, space); } + +// Function to recursively remove 'address' properties and lowercase string properties +export function normalizeConfig(obj: any): any { + if (Array.isArray(obj)) { + return obj.map(normalizeConfig); + } else if (obj !== null && typeof obj === 'object') { + const newObj: any = {}; + for (const key in obj) { + if (key !== 'address') { + newObj[key] = key === 'type' ? obj[key] : normalizeConfig(obj[key]); + } + } + return newObj; + } else if (typeof obj === 'string') { + return obj.toLowerCase(); + } + + return obj; +} + +export function configDeepEquals(v1: any, v2: any): boolean { + try { + deepStrictEqual(v1, v2); + return true; + } catch (error) { + rootLogger.info((error as Error).message); + return false; + } +} From 44a2ffa1b647222878f745743e079703ebec3819 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Noah=20Bayindirli=20=F0=9F=A5=82?= Date: Fri, 31 May 2024 12:32:22 -0400 Subject: [PATCH 13/73] feat(cli): add 'hyperlane core read' command (#3867) ### Description * adds new `hyperlane core read` command * this replaces previous `hl ism read` & `hl hook read` control flows ### Related issues - fixes https://github.com/hyperlane-xyz/issues/issues/1185 ### Backward compatibility * yes ### Testing * [x] manual * [ ] ci-test --- .changeset/three-coats-juggle.md | 5 +++ typescript/cli/cli.ts | 4 -- typescript/cli/src/commands/core.ts | 66 +++++++++++++++++++++++++++-- typescript/cli/src/commands/hook.ts | 47 -------------------- typescript/cli/src/commands/ism.ts | 52 ----------------------- 5 files changed, 68 insertions(+), 106 deletions(-) create mode 100644 .changeset/three-coats-juggle.md delete mode 100644 typescript/cli/src/commands/hook.ts delete mode 100644 typescript/cli/src/commands/ism.ts diff --git a/.changeset/three-coats-juggle.md b/.changeset/three-coats-juggle.md new file mode 100644 index 000000000..1d70f125f --- /dev/null +++ b/.changeset/three-coats-juggle.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/cli': minor +--- + +Adds 'hyperlane core read'. diff --git a/typescript/cli/cli.ts b/typescript/cli/cli.ts index 708dfcaef..9ba398e02 100644 --- a/typescript/cli/cli.ts +++ b/typescript/cli/cli.ts @@ -10,8 +10,6 @@ import { chainsCommand } from './src/commands/chains.js'; import { configCommand } from './src/commands/config.js'; import { coreCommand } from './src/commands/core.js'; import { deployCommand } from './src/commands/deploy.js'; -import { hookCommand } from './src/commands/hook.js'; -import { ismCommand } from './src/commands/ism.js'; import { keyCommandOption, logFormatCommandOption, @@ -57,8 +55,6 @@ try { .command(configCommand) .command(coreCommand) .command(deployCommand) - .command(hookCommand) - .command(ismCommand) .command(sendCommand) .command(statusCommand) .command(validatorCommand) diff --git a/typescript/cli/src/commands/core.ts b/typescript/cli/src/commands/core.ts index 6947ce494..80547deaa 100644 --- a/typescript/cli/src/commands/core.ts +++ b/typescript/cli/src/commands/core.ts @@ -3,9 +3,15 @@ import { CommandModule } from 'yargs'; import { createHooksConfigMap } from '../config/hooks.js'; import { createIsmConfigMap } from '../config/ism.js'; import { CommandModuleWithContext } from '../context/types.js'; -import { log } from '../logger.js'; +import { readHookConfig } from '../hook/read.js'; +import { readIsmConfig } from '../ism/read.js'; +import { log, warnYellow } from '../logger.js'; -import { outputFileCommandOption } from './options.js'; +import { + addressCommandOption, + chainCommandOption, + outputFileCommandOption, +} from './options.js'; /** * Parent command @@ -13,7 +19,8 @@ import { outputFileCommandOption } from './options.js'; export const coreCommand: CommandModule = { command: 'core', describe: 'Manage core Hyperlane contracts & configs', - builder: (yargs) => yargs.command(config).version(false).demandCommand(), + builder: (yargs) => + yargs.command(config).command(read).version(false).demandCommand(), handler: () => log('Command required'), }; @@ -47,3 +54,56 @@ export const config: CommandModuleWithContext<{ process.exit(0); }, }; + +export const read: CommandModuleWithContext<{ + chain: string; + ismAddress: string; + hookAddress: string; + ismOut: string; + hookOut: string; +}> = { + command: 'read', + describe: 'Reads onchain ISM & Hook configurations for given addresses', + builder: { + chain: { + ...chainCommandOption, + demandOption: true, + }, + ismAddress: addressCommandOption( + 'Address of the Interchain Security Module to read.', + false, + ), + hookAddress: addressCommandOption('Address of the Hook to read.', false), + ismOut: outputFileCommandOption(), + hookOut: outputFileCommandOption(), + }, + handler: async ({ + context, + chain, + ismAddress, + hookAddress, + ismOut, + hookOut, + }) => { + if (ismAddress) + await readIsmConfig({ + context, + chain, + address: ismAddress, + out: ismOut, + }); + if (hookAddress) + await readHookConfig({ + context, + chain, + address: hookAddress, + out: hookOut, + }); + + if (!ismAddress && !hookAddress) + warnYellow( + 'Must provide --ism-address, --hook-address, or both to read.', + ); + process.exit(0); + }, +}; diff --git a/typescript/cli/src/commands/hook.ts b/typescript/cli/src/commands/hook.ts deleted file mode 100644 index 34410dcbd..000000000 --- a/typescript/cli/src/commands/hook.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { CommandModule } from 'yargs'; - -import { CommandModuleWithContext } from '../context/types.js'; -import { readHookConfig } from '../hook/read.js'; -import { log } from '../logger.js'; - -import { - addressCommandOption, - chainCommandOption, - outputFileCommandOption, -} from './options.js'; - -/** - * Parent command - */ -export const hookCommand: CommandModule = { - command: 'hook', - describe: 'Operations relating to Hooks', - builder: (yargs) => yargs.command(read).version(false).demandCommand(), - handler: () => log('Command required'), -}; - -// Examples for testing: -// Fallback routing hook on polygon (may take 5s): -// hyperlane hook read --chain polygon --address 0xca4cCe24E7e06241846F5EA0cda9947F0507C40C -// IGP hook on inevm (may take 5s): -// hyperlane hook read --chain inevm --address 0x19dc38aeae620380430C200a6E990D5Af5480117 -export const read: CommandModuleWithContext<{ - chain: string; - address: string; - out: string; -}> = { - command: 'read', - describe: 'Reads onchain Hook configuration for a given address', - builder: { - chain: { - ...chainCommandOption, - demandOption: true, - }, - address: addressCommandOption('Address of the Hook to read.', true), - out: outputFileCommandOption(), - }, - handler: async (args) => { - await readHookConfig(args); - process.exit(0); - }, -}; diff --git a/typescript/cli/src/commands/ism.ts b/typescript/cli/src/commands/ism.ts deleted file mode 100644 index 831ea7207..000000000 --- a/typescript/cli/src/commands/ism.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { CommandModule } from 'yargs'; - -import { CommandModuleWithContext } from '../context/types.js'; -import { readIsmConfig } from '../ism/read.js'; -import { log } from '../logger.js'; - -import { - addressCommandOption, - chainCommandOption, - outputFileCommandOption, -} from './options.js'; - -/** - * Parent command - */ -export const ismCommand: CommandModule = { - command: 'ism', - describe: 'Operations relating to ISMs', - builder: (yargs) => yargs.command(read).version(false).demandCommand(), - handler: () => log('Command required'), -}; - -// Examples for testing: -// Top-level aggregation ISM on celo (may take 10s) -// hyperlane ism read --chain celo --address 0x99e8E56Dce3402D6E09A82718937fc1cA2A9491E -// Aggregation ISM for bsc domain on inevm (may take 5s) -// hyperlane ism read --chain inevm --address 0x79A7c7Fe443971CBc6baD623Fdf8019C379a7178 -// Test ISM on alfajores testnet -// hyperlane ism read --chain alfajores --address 0xdB52E4853b6A40D2972E6797E0BDBDb3eB761966 -export const read: CommandModuleWithContext<{ - chain: string; - address: string; - out: string; -}> = { - command: 'read', - describe: 'Reads onchain ISM configuration for a given address', - builder: { - chain: { - ...chainCommandOption, - demandOption: true, - }, - address: addressCommandOption( - 'Address of the Interchain Security Module to read.', - true, - ), - out: outputFileCommandOption(), - }, - handler: async (argv) => { - await readIsmConfig(argv); - process.exit(0); - }, -}; From 7089c910f3b14affd889569e42a7c8f19c4eee76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Noah=20Bayindirli=20=F0=9F=A5=82?= Date: Fri, 31 May 2024 15:01:46 -0400 Subject: [PATCH 14/73] feat(cli): add 'hyperlane warp read' command (#3868) ### Description * adds new `hyperlane warp read` command ### Related issues - fixes https://github.com/hyperlane-xyz/issues/issues/1189 ### Backward compatibility * yes ### Testing * [x] manual * [ ] ci-test --- .changeset/spotty-wolves-fail.md | 5 +++ typescript/cli/src/commands/core.ts | 8 +++- typescript/cli/src/commands/options.ts | 6 ++- typescript/cli/src/commands/warp.ts | 55 ++++++++++++++++++++++++-- typescript/sdk/src/index.ts | 1 + 5 files changed, 70 insertions(+), 5 deletions(-) create mode 100644 .changeset/spotty-wolves-fail.md diff --git a/.changeset/spotty-wolves-fail.md b/.changeset/spotty-wolves-fail.md new file mode 100644 index 000000000..73f74a044 --- /dev/null +++ b/.changeset/spotty-wolves-fail.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/cli': minor +--- + +Adds 'hyperlane warp read'. diff --git a/typescript/cli/src/commands/core.ts b/typescript/cli/src/commands/core.ts index 80547deaa..d033fa8d7 100644 --- a/typescript/cli/src/commands/core.ts +++ b/typescript/cli/src/commands/core.ts @@ -5,7 +5,7 @@ import { createIsmConfigMap } from '../config/ism.js'; import { CommandModuleWithContext } from '../context/types.js'; import { readHookConfig } from '../hook/read.js'; import { readIsmConfig } from '../ism/read.js'; -import { log, warnYellow } from '../logger.js'; +import { log, logGray, warnYellow } from '../logger.js'; import { addressCommandOption, @@ -41,6 +41,9 @@ export const config: CommandModuleWithContext<{ hooksOut: outputFileCommandOption('./configs/hooks.yaml'), }, handler: async ({ context, ismAdvanced, ismOut, hooksOut }) => { + logGray('Hyperlane Core Configure'); + logGray('------------------------'); + await createIsmConfigMap({ context, outPath: ismOut, @@ -85,6 +88,9 @@ export const read: CommandModuleWithContext<{ ismOut, hookOut, }) => { + logGray('Hyperlane Core Read'); + logGray('-------------------'); + if (ismAddress) await readIsmConfig({ context, diff --git a/typescript/cli/src/commands/options.ts b/typescript/cli/src/commands/options.ts index 3774b43cb..daed7ac4d 100644 --- a/typescript/cli/src/commands/options.ts +++ b/typescript/cli/src/commands/options.ts @@ -106,11 +106,15 @@ export const agentConfigCommandOption = ( default: defaultPath, }); -export const outputFileCommandOption = (defaultPath?: string): Options => ({ +export const outputFileCommandOption = ( + defaultPath?: string, + demandOption = false, +): Options => ({ type: 'string', description: 'Output file path', default: defaultPath, alias: 'o', + demandOption, }); export const inputFileCommandOption: Options = { diff --git a/typescript/cli/src/commands/warp.ts b/typescript/cli/src/commands/warp.ts index d568fe2c1..c820ddd0d 100644 --- a/typescript/cli/src/commands/warp.ts +++ b/typescript/cli/src/commands/warp.ts @@ -1,10 +1,17 @@ import { CommandModule } from 'yargs'; +import { EvmERC20WarpRouteReader } from '@hyperlane-xyz/sdk'; + import { createWarpRouteDeployConfig } from '../config/warp.js'; import { CommandModuleWithContext } from '../context/types.js'; -import { log } from '../logger.js'; +import { log, logGray, logGreen } from '../logger.js'; +import { writeFileAtPath } from '../utils/files.js'; -import { outputFileCommandOption } from './options.js'; +import { + addressCommandOption, + chainCommandOption, + outputFileCommandOption, +} from './options.js'; /** * Parent command @@ -12,7 +19,8 @@ import { outputFileCommandOption } from './options.js'; export const warpCommand: CommandModule = { command: 'warp', describe: 'Manage Hyperlane warp routes', - builder: (yargs) => yargs.command(config).version(false).demandCommand(), + builder: (yargs) => + yargs.command(config).command(read).version(false).demandCommand(), handler: () => log('Command required'), }; @@ -39,3 +47,44 @@ export const config: CommandModuleWithContext<{ process.exit(0); }, }; + +export const read: CommandModuleWithContext<{ + chain: string; + address: string; + out: string; +}> = { + command: 'read', + describe: 'Reads the warp route config at the given path.', + builder: { + chain: { + ...chainCommandOption, + demandOption: true, + }, + address: addressCommandOption( + 'Address of the router contract to read.', + true, + ), + out: outputFileCommandOption(), + }, + handler: async ({ context, chain, address, out }) => { + logGray('Hyperlane Warp Reader'); + logGray('---------------------'); + + const { multiProvider } = context; + const evmERC20WarpRouteReader = new EvmERC20WarpRouteReader( + multiProvider, + chain, + ); + const warpRouteConfig = await evmERC20WarpRouteReader.deriveWarpRouteConfig( + address, + ); + if (out) { + writeFileAtPath(out, JSON.stringify(warpRouteConfig, null, 4) + '\n'); + logGreen(`✅ Warp route config written successfully to ${out}.`); + } else { + logGreen(`✅ Warp route config read successfully:`); + log(JSON.stringify(warpRouteConfig, null, 4)); + } + process.exit(0); + }, +}; diff --git a/typescript/sdk/src/index.ts b/typescript/sdk/src/index.ts index 3c45dab6b..80f78f9ef 100644 --- a/typescript/sdk/src/index.ts +++ b/typescript/sdk/src/index.ts @@ -422,6 +422,7 @@ export { TokenFactories, } from './token/contracts.js'; export { HypERC20Deployer, HypERC721Deployer } from './token/deploy.js'; +export { EvmERC20WarpRouteReader } from './token/EvmERC20WarpRouteReader.js'; export { ChainMap, ChainName, ChainNameOrId, Connection } from './types.js'; export { MultiGeneric } from './utils/MultiGeneric.js'; export { getCosmosRegistryChain } from './utils/cosmos.js'; From 1d0d1bb36bda9568bae882695647c642c2281090 Mon Sep 17 00:00:00 2001 From: Lee <6251863+ltyu@users.noreply.github.com> Date: Mon, 3 Jun 2024 13:12:40 -0400 Subject: [PATCH 15/73] feat: implement hyperlane core deploy (#3869) ### Description - Adds `hyperlane core deploy` to use EvmCoreModule - Legacy `hyperlane deploy core` is retained to keep cli e2e test working - `hyperlane core deploy` expects to deploy to only a single chain ### Drive-by changes ### Related issues - Fixes https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/3539 ### Backward compatibility yes, legacy `hyperlane deploy core` is retained ### Testing - Cli integration - Manual --- .changeset/strong-rockets-kiss.md | 6 + typescript/cli/ci-test.sh | 33 +- typescript/cli/examples/core-config.yaml | 19 + typescript/cli/src/commands/core.ts | 8 +- typescript/cli/src/commands/deploy.ts | 51 +- typescript/cli/src/context/context.ts | 5 +- typescript/cli/src/deploy/core.ts | 464 ++---------------- typescript/cli/src/deploy/dry-run.ts | 1 + typescript/cli/src/deploy/utils.ts | 28 +- .../src/core/EvmCoreModule.hardhat-test.ts | 38 +- typescript/sdk/src/core/EvmCoreModule.ts | 13 +- typescript/sdk/src/index.ts | 2 + 12 files changed, 180 insertions(+), 488 deletions(-) create mode 100644 .changeset/strong-rockets-kiss.md create mode 100644 typescript/cli/examples/core-config.yaml diff --git a/.changeset/strong-rockets-kiss.md b/.changeset/strong-rockets-kiss.md new file mode 100644 index 000000000..48a1d1948 --- /dev/null +++ b/.changeset/strong-rockets-kiss.md @@ -0,0 +1,6 @@ +--- +'@hyperlane-xyz/cli': minor +'@hyperlane-xyz/sdk': minor +--- + +Implements `hyperlane core deploy` diff --git a/typescript/cli/ci-test.sh b/typescript/cli/ci-test.sh index f2b12371e..58019fdbb 100755 --- a/typescript/cli/ci-test.sh +++ b/typescript/cli/ci-test.sh @@ -34,11 +34,11 @@ _main() { run_hyperlane_deploy_warp; run_hyperlane_send_message; - cd ./rust; + # cd ./rust; - run_validator; - run_relayer; - run_hyperlane_status; + # run_validator; + # run_relayer; + # run_hyperlane_status; kill_anvil; @@ -75,6 +75,7 @@ prepare_environment_vars() { } prepare_anvil() { + CHAIN1_PORT=8545 CHAIN2_PORT=8555 @@ -142,12 +143,11 @@ run_hyperlane_deploy_core_dry_run() { update_deployer_balance; echo -e "\nDry-running contract deployments to Alfajores" - yarn workspace @hyperlane-xyz/cli run hyperlane deploy core \ + yarn workspace @hyperlane-xyz/cli run hyperlane core deploy \ --dry-run alfajores \ --registry ${TEST_CONFIGS_PATH}/dry-run \ --overrides " " \ - $(if [ "$HOOK_FLAG" == "true" ]; then echo "--hook ${EXAMPLES_PATH}/hooks.yaml"; fi) \ - --ism ${TEST_CONFIGS_PATH}/dry-run/ism.yaml \ + --config ${EXAMPLES_PATH}/core-config.yaml \ --from-address 0xfaD1C94469700833717Fa8a3017278BC1cA8031C \ --yes @@ -175,14 +175,21 @@ run_hyperlane_deploy_warp_dry_run() { run_hyperlane_deploy_core() { update_deployer_balance; - echo -e "\nDeploying contracts to ${CHAIN1} and ${CHAIN2}" - yarn workspace @hyperlane-xyz/cli run hyperlane deploy core \ + echo -e "\nDeploying contracts to ${CHAIN1}" + yarn workspace @hyperlane-xyz/cli run hyperlane core deploy \ + --registry $REGISTRY_PATH \ + --overrides " " \ + --config ${EXAMPLES_PATH}/core-config.yaml \ + --chain $CHAIN1 \ + --key $ANVIL_KEY \ + --yes + + echo -e "\nDeploying contracts to ${CHAIN2}" + yarn workspace @hyperlane-xyz/cli run hyperlane core deploy \ --registry $REGISTRY_PATH \ --overrides " " \ - --targets ${CHAIN1},${CHAIN2} \ - $(if [ "$HOOK_FLAG" == "true" ]; then echo "--hook ${EXAMPLES_PATH}/hooks.yaml"; fi) \ - --ism $CORE_ISM_PATH \ - --agent /tmp/agent-config.json \ + --config ${EXAMPLES_PATH}/core-config.yaml \ + --chain $CHAIN2 \ --key $ANVIL_KEY \ --yes diff --git a/typescript/cli/examples/core-config.yaml b/typescript/cli/examples/core-config.yaml new file mode 100644 index 000000000..cfc548543 --- /dev/null +++ b/typescript/cli/examples/core-config.yaml @@ -0,0 +1,19 @@ +# A config to define the core contract deployments +owner: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' +defaultIsm: + type: 'testIsm' + threshold: 1 # Number: Signatures required to approve a message + validators: # Array: List of validator addresses + - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' +defaultHook: + type: protocolFee + maxProtocolFee: '1000000000000000000' # in wei (string) + protocolFee: '200000000000000' # in wei (string) + beneficiary: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' + owner: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' +requiredHook: + type: protocolFee + maxProtocolFee: '1000000000000000000' # in wei (string) + protocolFee: '200000000000000' # in wei (string) + beneficiary: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' + owner: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' diff --git a/typescript/cli/src/commands/core.ts b/typescript/cli/src/commands/core.ts index d033fa8d7..eeaf1613d 100644 --- a/typescript/cli/src/commands/core.ts +++ b/typescript/cli/src/commands/core.ts @@ -7,6 +7,7 @@ import { readHookConfig } from '../hook/read.js'; import { readIsmConfig } from '../ism/read.js'; import { log, logGray, warnYellow } from '../logger.js'; +import { deployCore } from './deploy.js'; import { addressCommandOption, chainCommandOption, @@ -20,7 +21,12 @@ export const coreCommand: CommandModule = { command: 'core', describe: 'Manage core Hyperlane contracts & configs', builder: (yargs) => - yargs.command(config).command(read).version(false).demandCommand(), + yargs + .command(deployCore) + .command(config) + .command(read) + .version(false) + .demandCommand(), handler: () => log('Command required'), }; diff --git a/typescript/cli/src/commands/deploy.ts b/typescript/cli/src/commands/deploy.ts index 7de7fb182..b47ae7bf0 100644 --- a/typescript/cli/src/commands/deploy.ts +++ b/typescript/cli/src/commands/deploy.ts @@ -9,15 +9,14 @@ import { runCoreDeploy } from '../deploy/core.js'; import { evaluateIfDryRunFailure } from '../deploy/dry-run.js'; import { runWarpRouteDeploy } from '../deploy/warp.js'; import { log, logGray } from '../logger.js'; +import { readYamlOrJson } from '../utils/files.js'; import { agentConfigCommandOption, agentTargetsCommandOption, - coreTargetsCommandOption, + chainCommandOption, dryRunCommandOption, fromAddressCommandOption, - hookCommandOption, - ismCommandOption, originCommandOption, warpDeploymentConfigCommandOption, } from './options.js'; @@ -30,7 +29,6 @@ export const deployCommand: CommandModule = { describe: 'Permissionlessly deploy a Hyperlane contracts or extensions', builder: (yargs) => yargs - .command(coreCommand) .command(warpCommand) .command(agentCommand) .version(false) @@ -67,40 +65,39 @@ const agentCommand: CommandModuleWithContext<{ }; /** - * Core command + * Generates a command module for deploying Hyperlane contracts, given a command + * + * @param commandName - the deploy command key used to look up the deployFunction + * @returns A command module used to deploy Hyperlane contracts. */ -const coreCommand: CommandModuleWithWriteContext<{ - targets: string; - ism?: string; - hook?: string; - 'dry-run': string; - 'from-address': string; - agent: string; +export const deployCore: CommandModuleWithWriteContext<{ + chain: string; + config: string; + dryRun: string; + fromAddress: string; }> = { - command: 'core', - describe: 'Deploy core Hyperlane contracts', + command: 'deploy', + describe: 'Deploy Hyperlane contracts', builder: { - targets: coreTargetsCommandOption, - ism: ismCommandOption, - hook: hookCommandOption, - agent: agentConfigCommandOption(false, './configs/agent.json'), + chain: chainCommandOption, + config: { + type: 'string', + description: + 'The path to a JSON or YAML file with a core deployment config.', + demandOption: true, + }, 'dry-run': dryRunCommandOption, 'from-address': fromAddressCommandOption, }, - handler: async ({ context, targets, ism, hook, agent, dryRun }) => { - logGray( - `Hyperlane permissionless core deployment${dryRun ? ' dry-run' : ''}`, - ); + handler: async ({ context, chain, config: configFilePath, dryRun }) => { + logGray(`Hyperlane permissionless deployment${dryRun ? ' dry-run' : ''}`); logGray(`------------------------------------------------`); try { - const chains = targets?.split(',').map((r: string) => r.trim()); await runCoreDeploy({ context, - chains, - ismConfigPath: ism, - hookConfigPath: hook, - agentOutPath: agent, + chain, + config: readYamlOrJson(configFilePath), }); } catch (error: any) { evaluateIfDryRunFailure(error, dryRun); diff --git a/typescript/cli/src/context/context.ts b/typescript/cli/src/context/context.ts index fba159a69..23291a8ce 100644 --- a/typescript/cli/src/context/context.ts +++ b/typescript/cli/src/context/context.ts @@ -99,9 +99,8 @@ export async function getDryRunContext( logBlue(`Dry-running against chain: ${chain}`); await verifyAnvil(); - const multiProvider = await getMultiProvider(registry); - await forkNetworkToMultiProvider(multiProvider, chain); - + let multiProvider = await getMultiProvider(registry); + multiProvider = await forkNetworkToMultiProvider(multiProvider, chain); const { impersonatedKey, impersonatedSigner } = await getImpersonatedSigner({ fromAddress, key, diff --git a/typescript/cli/src/deploy/core.ts b/typescript/cli/src/deploy/core.ts index 54edc8055..d2d8b3587 100644 --- a/typescript/cli/src/deploy/core.ts +++ b/typescript/cli/src/deploy/core.ts @@ -1,463 +1,87 @@ -import { confirm } from '@inquirer/prompts'; -import { ethers } from 'ethers'; +import { ChainName, CoreConfig, EvmCoreModule } from '@hyperlane-xyz/sdk'; -import { ChainAddresses, IRegistry } from '@hyperlane-xyz/registry'; -import { - ChainMap, - ChainName, - CoreConfig, - HookType, - HyperlaneAddressesMap, - HyperlaneContractsMap, - HyperlaneCore, - HyperlaneCoreDeployer, - HyperlaneIsmFactory, - HyperlaneProxyFactoryDeployer, - IgpConfig, - IsmConfig, - IsmType, - MultisigConfig, - RoutingIsmConfig, - buildAgentConfig, - buildAggregationIsmConfigs, - defaultMultisigConfigs, - multisigIsmVerificationCost, - serializeContractsMap, -} from '@hyperlane-xyz/sdk'; -import { Address, objFilter, objMap, objMerge } from '@hyperlane-xyz/utils'; - -import { - HooksConfig, - 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 { WriteCommandContext } from '../context/types.js'; -import { - log, - logBlue, - logBoldUnderlinedRed, - logGray, - logGreen, - logRed, -} from '../logger.js'; -import { runMultiChainSelectionStep } from '../utils/chains.js'; -import { runFileSelectionStep, writeJson } from '../utils/files.js'; +import { logBlue } from '../logger.js'; +import { runSingleChainSelectionStep } from '../utils/chains.js'; import { completeDeploy, - isISMConfig, - isZODISMConfig, prepareDeploy, + runDeployPlanStep, runPreflightChecksForChains, } from './utils.js'; -const CONTRACT_CACHE_EXCLUSIONS = ['interchainGasPaymaster']; - +interface DeployParams { + context: WriteCommandContext; + chain: ChainName; + config: CoreConfig; +} /** * Executes the core deploy command. */ export async function runCoreDeploy({ context, - chains, - ismConfigPath, - hookConfigPath, - agentOutPath, + chain, + config, }: { context: WriteCommandContext; - chains?: ChainName[]; - ismConfigPath?: string; - hookConfigPath?: string; - agentOutPath: string; + chain: ChainName; + config: CoreConfig; }) { - const { chainMetadata, signer, dryRunChain, skipConfirmation } = context; - - if (dryRunChain) chains = [dryRunChain]; - else if (!chains?.length) { - if (skipConfirmation) throw new Error('No chains provided'); - chains = await runMultiChainSelectionStep( + const { + signer, + isDryRun, + chainMetadata, + dryRunChain, + registry, + skipConfirmation, + } = context; + + // Select a dry-run chain if it's not supplied + if (dryRunChain) { + chain = dryRunChain; + } else if (!chain) { + if (skipConfirmation) throw new Error('No chain provided'); + chain = await runSingleChainSelectionStep( chainMetadata, - 'Select chains to connect:', - true, + 'Select chain to connect:', ); } - - 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 = { context, - chains, - ismConfigs, - multisigConfigs, - hooksConfig, - agentOutPath, + chain, + config, }; await runDeployPlanStep(deploymentParams); await runPreflightChecksForChains({ ...deploymentParams, + chains: [chain], minGas: MINIMUM_CORE_DEPLOY_GAS, }); const userAddress = await signer.getAddress(); - const initialBalances = await prepareDeploy(context, userAddress, chains); - - await executeDeploy(deploymentParams); - - await completeDeploy(context, 'core', initialBalances, userAddress, chains); -} - -async function runIsmStep( - selectedChains: ChainName[], - skipConfirmation: boolean, - ismConfigPath?: string, -) { - if (!ismConfigPath) { - logBlue( - '\n', - 'Hyperlane instances requires an Interchain Security Module (ISM).', - ); - logGray( - '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', - ); - } - - 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 as ChainMap; - } -} - -async function runHookStep( - _selectedChains: ChainName[], - hookConfigPath?: string, -) { - if (!hookConfigPath) return {}; - return readHooksConfigMap(hookConfigPath); -} - -interface DeployParams { - context: WriteCommandContext; - chains: ChainName[]; - ismConfigs?: ChainMap; - multisigConfigs?: ChainMap; - hooksConfig?: ChainMap; - agentOutPath: string; -} - -async function runDeployPlanStep({ context, chains }: DeployParams) { - const { signer, skipConfirmation } = context; - const address = await signer.getAddress(); - - logBlue('\nDeployment plan'); - logGray('==============='); - log(`Transaction signer and owner of new contracts will be ${address}`); - log(`Deploying to ${chains.join(', ')}`); - log( - `There are several contracts required for each chain but contracts in your provided registries will be skipped`, - ); + const initialBalances = await prepareDeploy(context, userAddress, [chain]); - if (skipConfirmation) return; - const isConfirmed = await confirm({ - message: 'Is this deployment plan correct?', - }); - if (!isConfirmed) throw new Error('Deployment cancelled'); -} - -async function executeDeploy({ - context, - chains, - ismConfigs = {}, - multisigConfigs = {}, - hooksConfig = {}, - agentOutPath, -}: DeployParams) { logBlue('All systems ready, captain! Beginning deployment...'); - const { signer, multiProvider, registry } = context; - - let chainAddresses = await registry.getAddresses(); - chainAddresses = filterAddressesToCache(chainAddresses); - - const owner = await signer.getAddress(); - let artifacts: HyperlaneAddressesMap = {}; - - // 1. Deploy ISM factories to all deployable chains that don't have them. - logBlue('Deploying ISM factory contracts'); - const ismFactoryDeployer = new HyperlaneProxyFactoryDeployer(multiProvider); - ismFactoryDeployer.cacheAddressesMap(chainAddresses); - - const ismFactoryConfig = chains.reduce((chainMap, curr) => { - chainMap[curr] = {}; - return chainMap; - }, {} as ChainMap<{}>); - const ismFactoryContracts = await ismFactoryDeployer.deploy(ismFactoryConfig); - - artifacts = await updateChainAddresses( - registry, - ismFactoryContracts, - artifacts, - context.isDryRun, - ); - - logGreen('ISM factory contracts deployed'); - - // Build an IsmFactory that covers all chains so that we can - // use it to deploy ISMs to remote chains. - const ismFactory = HyperlaneIsmFactory.fromAddressesMap( - chainAddresses, - multiProvider, - ); - // 3. Construct ISM configs for all deployable chains - const defaultIsms: ChainMap = {}; - for (const ismOrigin of chains) { - defaultIsms[ismOrigin] = - ismConfigs[ismOrigin] ?? - buildIsmConfig(owner, ismOrigin, chains, multisigConfigs); - } - - // 4. Deploy core contracts to chains - logBlue(`Deploying core contracts to ${chains.join(', ')}`); - const coreDeployer = new HyperlaneCoreDeployer(multiProvider, ismFactory); - coreDeployer.cacheAddressesMap(chainAddresses as any); - const coreConfigs = buildCoreConfigMap( - owner, - chains, - defaultIsms, - 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 = await updateChainAddresses( - registry, - coreContracts, - artifacts, - context.isDryRun, - ); - logGreen('✅ Core contracts deployed'); - log(JSON.stringify(artifacts, null, 2)); - - await writeAgentConfig(context, artifacts, chains, agentOutPath); - - logBlue('Deployment is complete!'); -} - -function filterAddressesToCache(addressesMap: ChainMap) { - // Filter out the certain addresses that must always be - // deployed when deploying to a PI chain. - // See https://github.com/hyperlane-xyz/hyperlane-monorepo/pull/2983 - // And https://github.com/hyperlane-xyz/hyperlane-monorepo/pull/3183 - return objMap(addressesMap, (_chain, addresses) => - objFilter( - addresses, - (contract, _address): _address is string => - !CONTRACT_CACHE_EXCLUSIONS.includes(contract), - ), - ); -} - -function buildIsmConfig( - owner: Address, - local: ChainName, - chains: ChainName[], - multisigIsmConfigs: ChainMap, -): RoutingIsmConfig { - const aggregationIsmConfigs = buildAggregationIsmConfigs( - local, - chains, - multisigIsmConfigs, - ); - return { - owner, - type: IsmType.ROUTING, - domains: aggregationIsmConfigs, - }; -} - -function buildCoreConfigMap( - owner: Address, - chains: ChainName[], - defaultIsms: ChainMap, - hooksConfig: ChainMap, -): ChainMap { - return chains.reduce>((config, chain) => { - const hooks = hooksConfig[chain] ?? presetHookConfigs(owner); - config[chain] = { - owner, - defaultIsm: defaultIsms[chain], - defaultHook: hooks.default, - requiredHook: hooks.required, - }; - return config; - }, {}); -} - -export function buildIgpConfigMap( - owner: Address, - chains: ChainName[], - multisigConfigs: ChainMap, -): ChainMap { - const configMap: ChainMap = {}; - for (const chain of chains) { - const overhead: 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( - threshold, - validatorsLength, - ); - } - configMap[chain] = { - type: HookType.INTERCHAIN_GAS_PAYMASTER, - owner, - beneficiary: owner, - overhead, - oracleKey: owner, - oracleConfig: {}, - }; - } - return configMap; -} - -async function updateChainAddresses( - registry: IRegistry, - newContracts: HyperlaneContractsMap, - otherAddresses: HyperlaneAddressesMap, - isDryRun?: boolean, -) { - let newAddresses = serializeContractsMap(newContracts); - // The HyperlaneCoreDeployer is returning a nested object with ISM addresses - // from other chains, which don't need to be in the artifacts atm. - newAddresses = objMap(newAddresses, (_, newChainAddresses) => { - // For each chain in the addresses chainmap, filter the values to those that are just strings - return objFilter( - newChainAddresses, - (_, value): value is string => typeof value === 'string', - ); + const evmCoreModule = await EvmCoreModule.create({ + chain, + config, + multiProvider: context.multiProvider, }); - const mergedAddresses = objMerge(otherAddresses, newAddresses); - if (isDryRun) return mergedAddresses; + await completeDeploy(context, 'core', initialBalances, userAddress, [chain]); + const deployedAddresses = evmCoreModule.serialize(); - for (const chainName of Object.keys(newContracts)) { + if (!isDryRun) { await registry.updateChain({ - chainName, - addresses: mergedAddresses[chainName], + chainName: chain, + addresses: deployedAddresses, }); - } - return mergedAddresses; -} -async function writeAgentConfig( - context: WriteCommandContext, - artifacts: HyperlaneAddressesMap, - chains: ChainName[], - outPath: string, -) { - if (context.isDryRun) return; - log('Writing agent configs'); - const { multiProvider, registry } = context; - const startBlocks: ChainMap = {}; - const core = HyperlaneCore.fromAddressesMap(artifacts, multiProvider); - - for (const chain of chains) { - const mailbox = core.getContracts(chain).mailbox; - startBlocks[chain] = (await mailbox.deployedBlock()).toNumber(); - } - - const chainAddresses = await registry.getAddresses(); - for (const chain of chains) { - if (!chainAddresses[chain].interchainGasPaymaster) { - chainAddresses[chain].interchainGasPaymaster = - ethers.constants.AddressZero; - } + // @TODO implement writeAgentConfig } - const agentConfig = buildAgentConfig( - chains, // Use only the chains that were deployed to - multiProvider, - chainAddresses as any, - startBlocks, - ); - writeJson(outPath, agentConfig); - logGreen('Agent configs written'); + logBlue('Deployment is complete!'); } diff --git a/typescript/cli/src/deploy/dry-run.ts b/typescript/cli/src/deploy/dry-run.ts index 11cbe379b..a2c1fb259 100644 --- a/typescript/cli/src/deploy/dry-run.ts +++ b/typescript/cli/src/deploy/dry-run.ts @@ -25,6 +25,7 @@ export async function forkNetworkToMultiProvider( }); await setFork(multiProvider, chain); + return multiProvider; } /** diff --git a/typescript/cli/src/deploy/utils.ts b/typescript/cli/src/deploy/utils.ts index f1082b301..46f3a6474 100644 --- a/typescript/cli/src/deploy/utils.ts +++ b/typescript/cli/src/deploy/utils.ts @@ -1,3 +1,4 @@ +import { confirm } from '@inquirer/prompts'; import { BigNumber, ethers } from 'ethers'; import { @@ -11,7 +12,7 @@ import { Address, ProtocolType } from '@hyperlane-xyz/utils'; import { parseIsmConfig } from '../config/ism.js'; import { WriteCommandContext } from '../context/types.js'; -import { log, logGreen, logPink } from '../logger.js'; +import { log, logBlue, logGray, logGreen, logPink } from '../logger.js'; import { gasBalancesAreSufficient } from '../utils/balances.js'; import { ENV } from '../utils/env.js'; import { assertSigner } from '../utils/keys.js'; @@ -55,6 +56,31 @@ export async function runPreflightChecksForChains({ if (sufficient) logGreen('✅ Balances are sufficient'); } +export async function runDeployPlanStep({ + context, + chain, +}: { + context: WriteCommandContext; + chain: ChainName; +}) { + const { signer, skipConfirmation } = context; + const address = await signer.getAddress(); + + logBlue('\nDeployment plan'); + logGray('==============='); + log(`Transaction signer and owner of new contracts will be ${address}`); + log(`Deploying to ${chain}`); + log( + `There are several contracts required for each chain but contracts in your provided registries will be skipped`, + ); + + if (skipConfirmation) return; + const isConfirmed = await confirm({ + message: 'Is this deployment plan correct?', + }); + if (!isConfirmed) throw new Error('Deployment cancelled'); +} + // from parsed types export function isISMConfig( config: ChainMap | ChainMap, diff --git a/typescript/sdk/src/core/EvmCoreModule.hardhat-test.ts b/typescript/sdk/src/core/EvmCoreModule.hardhat-test.ts index 0d64c9080..9233c0455 100644 --- a/typescript/sdk/src/core/EvmCoreModule.hardhat-test.ts +++ b/typescript/sdk/src/core/EvmCoreModule.hardhat-test.ts @@ -19,6 +19,7 @@ import { testCoreConfig } from '../test/testUtils.js'; import { EvmCoreModule } from './EvmCoreModule.js'; describe('EvmCoreModule', async () => { + const CHAIN = TestChainName.test1; const DELAY = 1892391283182; let signer: SignerWithAddress; let multiProvider: MultiProvider; @@ -33,7 +34,7 @@ describe('EvmCoreModule', async () => { [signer] = await hre.ethers.getSigners(); multiProvider = MultiProvider.createTestMultiProvider({ signer }); const config = { - ...testCoreConfig([TestChainName.test1])[TestChainName.test1], + ...testCoreConfig([CHAIN])[CHAIN], upgrade: { timelock: { delay: DELAY, @@ -46,7 +47,7 @@ describe('EvmCoreModule', async () => { }; evmCoreModule = await EvmCoreModule.create({ - chain: TestChainName.test1, + chain: CHAIN, config, multiProvider, }); @@ -61,27 +62,27 @@ describe('EvmCoreModule', async () => { proxyAdminContract = ProxyAdmin__factory.connect( proxyAdmin!, - multiProvider.getProvider(TestChainName.test1), + multiProvider.getProvider(CHAIN), ); mailboxContract = Mailbox__factory.connect( mailbox!, - multiProvider.getProvider(TestChainName.test1), + multiProvider.getProvider(CHAIN), ); validatorAnnounceContract = ValidatorAnnounce__factory.connect( validatorAnnounce!, - multiProvider.getProvider(TestChainName.test1), + multiProvider.getProvider(CHAIN), ); testRecipientContract = TestRecipient__factory.connect( testRecipient!, - multiProvider.getProvider(TestChainName.test1), + multiProvider.getProvider(CHAIN), ); timelockControllerContract = TimelockController__factory.connect( timelockController!, - multiProvider.getProvider(TestChainName.test1), + multiProvider.getProvider(CHAIN), ); }); @@ -95,13 +96,12 @@ describe('EvmCoreModule', async () => { it('should deploy ISM factories', () => { // Each ISM factory - objMap( - evmCoreModule.serialize().ismFactoryFactories, - (_: any, factoryAddress: any) => { - expect(factoryAddress).to.exist; - expect(factoryAddress).to.not.equal(constants.AddressZero); - }, - ); + const deployedContracts = evmCoreModule.serialize(); + + objMap(deployedContracts as any, (_, address) => { + expect(address).to.exist; + expect(address).to.not.equal(constants.AddressZero); + }); }); it('should deploy proxyAdmin', () => { @@ -112,8 +112,14 @@ describe('EvmCoreModule', async () => { expect(await proxyAdminContract.owner()).to.equal(signer.address); }); - it('should deploy mailbox', () => { - expect(evmCoreModule.serialize().mailbox).to.exist; + it('should deploy mailbox', async () => { + const mailboxAddress = evmCoreModule.serialize().mailbox; + expect(mailboxAddress).to.exist; + + // Check that it's actually a mailbox by calling one of it's methods + expect(await mailboxContract.localDomain()).to.equal( + multiProvider.getChainId(CHAIN), + ); }); it('should set mailbox owner to proxyAdmin', async () => { diff --git a/typescript/sdk/src/core/EvmCoreModule.ts b/typescript/sdk/src/core/EvmCoreModule.ts index 18c9fd39d..6887fbd1d 100644 --- a/typescript/sdk/src/core/EvmCoreModule.ts +++ b/typescript/sdk/src/core/EvmCoreModule.ts @@ -26,18 +26,17 @@ import { EvmIcaModule } from './EvmIcaModule.js'; import { HyperlaneCoreDeployer } from './HyperlaneCoreDeployer.js'; import { CoreFactories } from './contracts.js'; -type DeployedAdresses = HyperlaneAddresses & { +export type DeployedCoreAdresses = HyperlaneAddresses & { testRecipient: Address; timelockController?: Address; // Can be optional because it is only deployed if config.upgrade = true interchainAccountRouter: Address; interchainAccountIsm: Address; - ismFactoryFactories: HyperlaneAddresses; -}; +} & HyperlaneAddresses; export class EvmCoreModule extends HyperlaneModule< ProtocolType.Ethereum, CoreConfig, - DeployedAdresses + DeployedCoreAdresses > { protected logger = rootLogger.child({ module: 'EvmCoreModule' }); protected coreReader: EvmCoreReader; @@ -45,7 +44,7 @@ export class EvmCoreModule extends HyperlaneModule< protected constructor( protected readonly multiProvider: MultiProvider, - args: HyperlaneModuleParams, + args: HyperlaneModuleParams, ) { super(args); this.coreReader = new EvmCoreReader(multiProvider, this.args.chain); @@ -99,7 +98,7 @@ export class EvmCoreModule extends HyperlaneModule< config: CoreConfig; multiProvider: MultiProvider; chain: ChainNameOrId; - }): Promise { + }): Promise { const { config, multiProvider, chain } = params; const chainName = multiProvider.getChainName(chain); @@ -171,7 +170,7 @@ export class EvmCoreModule extends HyperlaneModule< // Set Core & extra addresses return { - ismFactoryFactories, + ...ismFactoryFactories, proxyAdmin, mailbox: mailbox.address, interchainAccountRouter, diff --git a/typescript/sdk/src/index.ts b/typescript/sdk/src/index.ts index 80f78f9ef..5c09a2d06 100644 --- a/typescript/sdk/src/index.ts +++ b/typescript/sdk/src/index.ts @@ -473,3 +473,5 @@ export { S3Config, S3Wrapper, S3Receipt } from './aws/s3.js'; // prettier-ignore // @ts-ignore export { canProposeSafeTransactions, getSafe, getSafeDelegates, getSafeService } from './utils/gnosisSafe.js'; + +export { EvmCoreModule, DeployedCoreAdresses } from './core/EvmCoreModule.js'; From 4663018fc8f28910de5a2c8db5dd836de1c9b3a4 Mon Sep 17 00:00:00 2001 From: Lee <6251863+ltyu@users.noreply.github.com> Date: Mon, 3 Jun 2024 17:10:39 -0400 Subject: [PATCH 16/73] feat: implement hyperlane core config to return CoreConfig (#3879) ### Description - Updates `hl core config` to output a single `CoreConfig` file - Makes `CoreConfig` single chain ie. remove ChainMap - Removes `nativeTokenAndDecimals()` for fee calculation - Add description as a prameter for `outputFileCommandOption()` ### Related issues - Fixes #3877 ### Backward compatibility No, Outputs CoreConfig as a single file and no longer expecting 2 individual files ### Testing Manual --- .changeset/bright-islands-shake.md | 6 ++ typescript/cli/src/commands/core.ts | 70 +++++++++++---- typescript/cli/src/commands/deploy.ts | 14 +-- typescript/cli/src/commands/options.ts | 3 +- typescript/cli/src/config/hooks.ts | 85 +++---------------- typescript/cli/src/config/ism.ts | 82 +++--------------- typescript/cli/src/config/warp.ts | 8 +- .../sdk/src/deploy/HyperlaneDeployer.ts | 2 +- 8 files changed, 99 insertions(+), 171 deletions(-) create mode 100644 .changeset/bright-islands-shake.md diff --git a/.changeset/bright-islands-shake.md b/.changeset/bright-islands-shake.md new file mode 100644 index 000000000..80f10d2d7 --- /dev/null +++ b/.changeset/bright-islands-shake.md @@ -0,0 +1,6 @@ +--- +'@hyperlane-xyz/cli': minor +'@hyperlane-xyz/sdk': minor +--- + +Implement hyperlane core config to return CoreConfig diff --git a/typescript/cli/src/commands/core.ts b/typescript/cli/src/commands/core.ts index eeaf1613d..bc2a63426 100644 --- a/typescript/cli/src/commands/core.ts +++ b/typescript/cli/src/commands/core.ts @@ -1,13 +1,27 @@ import { CommandModule } from 'yargs'; -import { createHooksConfigMap } from '../config/hooks.js'; -import { createIsmConfigMap } from '../config/ism.js'; +import { + HookConfigSchema, + IsmConfig, + IsmConfigSchema, +} from '@hyperlane-xyz/sdk'; + +import { createHookConfig } from '../config/hooks.js'; +import { createIsmConfig, createTrustedRelayerConfig } from '../config/ism.js'; import { CommandModuleWithContext } from '../context/types.js'; import { readHookConfig } from '../hook/read.js'; import { readIsmConfig } from '../ism/read.js'; -import { log, logGray, warnYellow } from '../logger.js'; +import { + log, + logBlue, + logBoldUnderlinedRed, + logGray, + logRed, + warnYellow, +} from '../logger.js'; +import { writeYamlOrJson } from '../utils/files.js'; -import { deployCore } from './deploy.js'; +import { deploy } from './deploy.js'; import { addressCommandOption, chainCommandOption, @@ -22,7 +36,7 @@ export const coreCommand: CommandModule = { describe: 'Manage core Hyperlane contracts & configs', builder: (yargs) => yargs - .command(deployCore) + .command(deploy) .command(config) .command(read) .version(false) @@ -32,8 +46,7 @@ export const coreCommand: CommandModule = { export const config: CommandModuleWithContext<{ ismAdvanced: boolean; - ismOut: string; - hooksOut: string; + config: string; }> = { command: 'config', describe: 'Create a core configuration, including ISMs and hooks.', @@ -43,22 +56,45 @@ export const config: CommandModuleWithContext<{ describe: 'Create an advanced ISM & hook configuration', default: false, }, - ismOut: outputFileCommandOption('./configs/ism.yaml'), - hooksOut: outputFileCommandOption('./configs/hooks.yaml'), + config: outputFileCommandOption( + './configs/core-config.yaml', + false, + 'The path to a JSON or YAML file with a core deployment config.', + ), }, - handler: async ({ context, ismAdvanced, ismOut, hooksOut }) => { + handler: async ({ context, ismAdvanced, config }) => { logGray('Hyperlane Core Configure'); logGray('------------------------'); - await createIsmConfigMap({ + // Create default Ism config (advanced or trusted) + let defaultIsm: IsmConfig; + if (ismAdvanced) { + 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.', + ); + defaultIsm = await createIsmConfig(context); + } else { + defaultIsm = await createTrustedRelayerConfig(context); + } + + // Create default and required Hook config + const defaultHook = await createHookConfig( context, - outPath: ismOut, - shouldUseDefault: !ismAdvanced, - }); - await createHooksConfigMap({ + 'Select default hook type', + ); + const requiredHook = await createHookConfig( context, - outPath: hooksOut, - }); + 'Select required hook type', + ); + + // Validate + IsmConfigSchema.parse(defaultIsm); + HookConfigSchema.parse(requiredHook); + HookConfigSchema.parse(defaultHook); + + writeYamlOrJson(config, { defaultIsm, defaultHook, requiredHook }); process.exit(0); }, diff --git a/typescript/cli/src/commands/deploy.ts b/typescript/cli/src/commands/deploy.ts index b47ae7bf0..74a20ac3e 100644 --- a/typescript/cli/src/commands/deploy.ts +++ b/typescript/cli/src/commands/deploy.ts @@ -18,6 +18,7 @@ import { dryRunCommandOption, fromAddressCommandOption, originCommandOption, + outputFileCommandOption, warpDeploymentConfigCommandOption, } from './options.js'; @@ -70,7 +71,7 @@ const agentCommand: CommandModuleWithContext<{ * @param commandName - the deploy command key used to look up the deployFunction * @returns A command module used to deploy Hyperlane contracts. */ -export const deployCore: CommandModuleWithWriteContext<{ +export const deploy: CommandModuleWithWriteContext<{ chain: string; config: string; dryRun: string; @@ -80,12 +81,11 @@ export const deployCore: CommandModuleWithWriteContext<{ describe: 'Deploy Hyperlane contracts', builder: { chain: chainCommandOption, - config: { - type: 'string', - description: - 'The path to a JSON or YAML file with a core deployment config.', - demandOption: true, - }, + config: outputFileCommandOption( + './configs/core-config.yaml', + false, + 'The path to a JSON or YAML file with a core deployment config.', + ), 'dry-run': dryRunCommandOption, 'from-address': fromAddressCommandOption, }, diff --git a/typescript/cli/src/commands/options.ts b/typescript/cli/src/commands/options.ts index daed7ac4d..878a58585 100644 --- a/typescript/cli/src/commands/options.ts +++ b/typescript/cli/src/commands/options.ts @@ -109,9 +109,10 @@ export const agentConfigCommandOption = ( export const outputFileCommandOption = ( defaultPath?: string, demandOption = false, + description = 'Output file path', ): Options => ({ type: 'string', - description: 'Output file path', + description, default: defaultPath, alias: 'o', demandOption, diff --git a/typescript/cli/src/config/hooks.ts b/typescript/cli/src/config/hooks.ts index 01103dffd..ed7baf267 100644 --- a/typescript/cli/src/config/hooks.ts +++ b/typescript/cli/src/config/hooks.ts @@ -18,9 +18,9 @@ import { } from '@hyperlane-xyz/utils'; import { CommandContext } from '../context/types.js'; -import { errorRed, log, logBlue, logGreen, logRed } from '../logger.js'; +import { errorRed, logBlue, logGreen, logRed } from '../logger.js'; import { runMultiChainSelectionStep } from '../utils/chains.js'; -import { mergeYamlOrJson, readYamlOrJson } from '../utils/files.js'; +import { readYamlOrJson } from '../utils/files.js'; // TODO: deprecate in favor of CoreConfigSchema const HooksConfigSchema = z.object({ @@ -31,10 +31,6 @@ export type HooksConfig = z.infer; const HooksConfigMapSchema = z.record(HooksConfigSchema); export type HooksConfigMap = z.infer; -export function isValidHookConfigMap(config: any) { - return HooksConfigMapSchema.safeParse(config).success; -} - export function presetHookConfigs(owner: Address): HooksConfig { return { required: { @@ -65,46 +61,13 @@ export function readHooksConfigMap(filePath: string) { return hooks; } -export async function createHooksConfigMap({ - context, - outPath, -}: { - context: CommandContext; - outPath: string; -}) { - logBlue('Creating a new hook config'); - const chains = await runMultiChainSelectionStep(context.chainMetadata); - - const result: HooksConfigMap = {}; - for (const chain of chains) { - for (const hookRequirements of ['required', 'default']) { - log(`\nSet ${hookRequirements} hook for chain ${chain}:`); - const remotes = chains.filter((c) => c !== chain); - result[chain] = { - ...result[chain], - [hookRequirements]: await createHookConfig(context, chain, remotes), - }; - } - if (isValidHookConfigMap(result)) { - logGreen(`Hook config is valid, writing to file ${outPath}`); - mergeYamlOrJson(outPath, result); - } else { - errorRed( - `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( context: CommandContext, - chain: ChainName, - remotes: ChainName[], + selectMessage = 'Select hook type', ): Promise { let lastConfig: HookConfig; const hookType = await select({ - message: 'Select hook type', + message: selectMessage, choices: [ { value: HookType.MERKLE_TREE, @@ -141,23 +104,20 @@ export async function createHookConfig( if (hookType === HookType.MERKLE_TREE) { lastConfig = { type: HookType.MERKLE_TREE }; } else if (hookType === HookType.PROTOCOL_FEE) { - lastConfig = await createProtocolFeeConfig(context, chain); + lastConfig = await createProtocolFeeConfig(); // } else if (hookType === HookType.INTERCHAIN_GAS_PAYMASTER) { // lastConfig = await createIGPConfig(remotes); } else if (hookType === HookType.AGGREGATION) { - lastConfig = await createAggregationConfig(context, chain, remotes); + lastConfig = await createAggregationConfig(context); } else if (hookType === HookType.ROUTING) { - lastConfig = await createRoutingConfig(context, chain, remotes); + lastConfig = await createRoutingConfig(context); } else { throw new Error(`Invalid hook type: ${hookType}`); } return lastConfig; } -export async function createProtocolFeeConfig( - context: CommandContext, - chain: ChainName, -): Promise { +export async function createProtocolFeeConfig(): Promise { const owner = await input({ message: 'Enter owner address for protocol fee hook', }); @@ -178,18 +138,12 @@ export async function createProtocolFeeConfig( // TODO: input in gwei, wei, etc const maxProtocolFee = toWei( await input({ - message: `Enter max protocol fee ${nativeTokenAndDecimals( - context, - chain, - )} e.g. 1.0) for protocol fee hook`, + message: `Enter max protocol fee for protocol fee hook`, }), ); const protocolFee = toWei( await input({ - message: `Enter protocol fee in ${nativeTokenAndDecimals( - context, - chain, - )} e.g. 0.01) for protocol fee hook`, + message: `Enter protocol fee in for protocol fee hook`, }), ); if (BigNumberJs(protocolFee).gt(maxProtocolFee)) { @@ -253,8 +207,6 @@ export async function createIGPConfig( export async function createAggregationConfig( context: CommandContext, - chain: ChainName, - remotes: ChainName[], ): Promise { const hooksNum = parseInt( await input({ @@ -265,7 +217,7 @@ export async function createAggregationConfig( const hooks: Array = []; for (let i = 0; i < hooksNum; i++) { logBlue(`Creating hook ${i + 1} of ${hooksNum} ...`); - hooks.push(await createHookConfig(context, chain, remotes)); + hooks.push(await createHookConfig(context)); } return { type: HookType.AGGREGATION, @@ -275,20 +227,19 @@ export async function createAggregationConfig( export async function createRoutingConfig( context: CommandContext, - origin: ChainName, - remotes: ChainName[], ): Promise { const owner = await input({ message: 'Enter owner address for routing ISM', }); const ownerAddress = owner; + const chains = await runMultiChainSelectionStep(context.chainMetadata); const domainsMap: ChainMap = {}; - for (const chain of remotes) { + for (const chain of chains) { await confirm({ message: `You are about to configure hook for remote chain ${chain}. Continue?`, }); - const config = await createHookConfig(context, origin, remotes); + const config = await createHookConfig(context); domainsMap[chain] = config; } return { @@ -297,11 +248,3 @@ export async function createRoutingConfig( domains: domainsMap, }; } - -function nativeTokenAndDecimals(context: CommandContext, chain: ChainName) { - return `10^${ - context.chainMetadata[chain].nativeToken?.decimals ?? '18' - } which you cannot exceed (in ${ - context.chainMetadata[chain].nativeToken?.symbol ?? 'eth' - }`; -} diff --git a/typescript/cli/src/config/ism.ts b/typescript/cli/src/config/ism.ts index d2252fc62..bf49d22b6 100644 --- a/typescript/cli/src/config/ism.ts +++ b/typescript/cli/src/config/ism.ts @@ -4,7 +4,6 @@ import { z } from 'zod'; import { AggregationIsmConfig, ChainMap, - ChainName, IsmConfig, IsmConfigSchema, IsmType, @@ -13,19 +12,11 @@ import { } from '@hyperlane-xyz/sdk'; import { CommandContext } from '../context/types.js'; -import { - errorRed, - log, - logBlue, - logBoldUnderlinedRed, - logGreen, - logRed, -} from '../logger.js'; import { detectAndConfirmOrPrompt, runMultiChainSelectionStep, } from '../utils/chains.js'; -import { mergeYamlOrJson, readYamlOrJson } from '../utils/files.js'; +import { readYamlOrJson } from '../utils/files.js'; const IsmConfigMapSchema = z.record(IsmConfigSchema).refine( (ismConfigMap) => { @@ -49,8 +40,6 @@ const IsmConfigMapSchema = z.record(IsmConfigSchema).refine( }, ); -type IsmConfigMap = z.infer; - export function parseIsmConfig(filePath: string) { const config = readYamlOrJson(filePath); if (!config) throw new Error(`No ISM config found at ${filePath}`); @@ -70,51 +59,7 @@ export function readIsmConfig(filePath: string) { } export function isValildIsmConfig(config: any) { - return IsmConfigMapSchema.safeParse(config).success; -} - -export async function createIsmConfigMap({ - context, - outPath, - shouldUseDefault = true, -}: { - context: CommandContext; - outPath: string; - shouldUseDefault: boolean; -}) { - 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 chains = await runMultiChainSelectionStep( - context.chainMetadata, - 'Select chains to configure ISM for', - true, - ); - - const result: IsmConfigMap = {}; - for (const chain of chains) { - log(`\nSet values for chain ${chain}:`); - result[chain] = shouldUseDefault - ? await createTrustedRelayerConfig(context) - : await createIsmConfig(context, 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); - } 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'); - } + return IsmConfigSchema.safeParse(config).success; } const ISM_TYPE_DESCRIPTIONS: Record = { @@ -136,8 +81,6 @@ const ISM_TYPE_DESCRIPTIONS: Record = { export async function createIsmConfig( context: CommandContext, - remote: ChainName, - origins: ChainName[], ): Promise { const moduleType = await select({ message: 'Select ISM type', @@ -157,9 +100,9 @@ export async function createIsmConfig( moduleType === IsmType.ROUTING || moduleType === IsmType.FALLBACK_ROUTING ) { - return createRoutingConfig(context, moduleType, remote, origins); + return createRoutingConfig(context, moduleType); } else if (moduleType === IsmType.AGGREGATION) { - return createAggregationConfig(context, remote, origins); + return createAggregationConfig(context); } else if (moduleType === IsmType.TEST_ISM) { return { type: IsmType.TEST_ISM }; } else if (moduleType === IsmType.TRUSTED_RELAYER) { @@ -205,8 +148,6 @@ export async function createTrustedRelayerConfig( export async function createAggregationConfig( context: CommandContext, - remote: ChainName, - chains: ChainName[], ): Promise { const isms = parseInt( await input({ @@ -224,7 +165,7 @@ export async function createAggregationConfig( const modules: Array = []; for (let i = 0; i < isms; i++) { - modules.push(await createIsmConfig(context, remote, chains)); + modules.push(await createIsmConfig(context)); } return { type: IsmType.AGGREGATION, @@ -236,21 +177,24 @@ export async function createAggregationConfig( export async function createRoutingConfig( context: CommandContext, type: IsmType.ROUTING | IsmType.FALLBACK_ROUTING, - remote: ChainName, - chains: ChainName[], ): Promise { const owner = await input({ message: 'Enter owner address for routing ISM', }); const ownerAddress = owner; - const origins = chains.filter((chain) => chain !== remote); + + const chains = await runMultiChainSelectionStep( + context.chainMetadata, + 'Select chains to configure ISM for', + true, + ); const domainsMap: ChainMap = {}; - for (const chain of origins) { + for (const chain of chains) { await confirm({ message: `You are about to configure ISM from source chain ${chain}. Continue?`, }); - const config = await createIsmConfig(context, chain, chains); + const config = await createIsmConfig(context); domainsMap[chain] = config; } return { diff --git a/typescript/cli/src/config/warp.ts b/typescript/cli/src/config/warp.ts index fabb355d7..974c219ba 100644 --- a/typescript/cli/src/config/warp.ts +++ b/typescript/cli/src/config/warp.ts @@ -2,7 +2,6 @@ import { input, select } from '@inquirer/prompts'; import { ChainMap, - ChainName, IsmConfig, IsmType, MailboxClientConfig, @@ -144,8 +143,8 @@ export async function createWarpRouteDeployConfig({ ); const interchainSecurityModule = shouldUseDefault - ? await createDefaultWarpIsmConfig(context, chain) - : await createIsmConfig(context, chain, warpChains); + ? await createDefaultWarpIsmConfig(context) + : await createIsmConfig(context); switch (type) { case TokenType.collateral: @@ -199,13 +198,12 @@ export function readWarpRouteConfig(filePath: string): WarpCoreConfig { async function createDefaultWarpIsmConfig( context: CommandContext, - remote: ChainName, ): Promise { return { type: IsmType.AGGREGATION, modules: [ await createTrustedRelayerConfig(context), - await createRoutingConfig(context, IsmType.FALLBACK_ROUTING, remote, []), + await createRoutingConfig(context, IsmType.FALLBACK_ROUTING), ], threshold: 1, }; diff --git a/typescript/sdk/src/deploy/HyperlaneDeployer.ts b/typescript/sdk/src/deploy/HyperlaneDeployer.ts index dab04a3d7..2fa626f49 100644 --- a/typescript/sdk/src/deploy/HyperlaneDeployer.ts +++ b/typescript/sdk/src/deploy/HyperlaneDeployer.ts @@ -149,7 +149,7 @@ export abstract class HyperlaneDeployer< const deployPromise = runWithTimeout(this.chainTimeoutMs, async () => { const contracts = await this.deployContracts(chain, configMap[chain]); this.addDeployedContracts(chain, contracts); - this.logger.info({ chain }, 'Successfully deployed contracts'); + this.logger.info(`Successfully deployed contracts on ${chain}`); }); if (this.options.concurrentDeploy) { deployPromises.push(deployPromise); From ee28c0ebcc03023aa26afa8ce65e660fc2591e99 Mon Sep 17 00:00:00 2001 From: Lee <6251863+ltyu@users.noreply.github.com> Date: Mon, 3 Jun 2024 20:15:47 -0400 Subject: [PATCH 17/73] feat: implement hyperlane core read to return CoreConfig (#3881) ### Description - Updates `hl core read` to output a single `CoreConfig` file ### Related issues - Fixes #3880 ### Backward compatibility Yes ### Testing Manual --- typescript/cli/src/commands/core.ts | 66 +++++++++-------------------- typescript/cli/src/config/ism.ts | 4 -- 2 files changed, 20 insertions(+), 50 deletions(-) diff --git a/typescript/cli/src/commands/core.ts b/typescript/cli/src/commands/core.ts index bc2a63426..69f95fc1c 100644 --- a/typescript/cli/src/commands/core.ts +++ b/typescript/cli/src/commands/core.ts @@ -1,6 +1,7 @@ import { CommandModule } from 'yargs'; import { + EvmCoreReader, HookConfigSchema, IsmConfig, IsmConfigSchema, @@ -9,24 +10,17 @@ import { import { createHookConfig } from '../config/hooks.js'; import { createIsmConfig, createTrustedRelayerConfig } from '../config/ism.js'; import { CommandModuleWithContext } from '../context/types.js'; -import { readHookConfig } from '../hook/read.js'; -import { readIsmConfig } from '../ism/read.js'; import { log, logBlue, logBoldUnderlinedRed, logGray, logRed, - warnYellow, } from '../logger.js'; import { writeYamlOrJson } from '../utils/files.js'; import { deploy } from './deploy.js'; -import { - addressCommandOption, - chainCommandOption, - outputFileCommandOption, -} from './options.js'; +import { chainCommandOption, outputFileCommandOption } from './options.js'; /** * Parent command @@ -59,10 +53,10 @@ export const config: CommandModuleWithContext<{ config: outputFileCommandOption( './configs/core-config.yaml', false, - 'The path to a JSON or YAML file with a core deployment config.', + 'The path to output a Core Config JSON or YAML file.', ), }, - handler: async ({ context, ismAdvanced, config }) => { + handler: async ({ context, ismAdvanced, config: configFilePath }) => { logGray('Hyperlane Core Configure'); logGray('------------------------'); @@ -94,7 +88,7 @@ export const config: CommandModuleWithContext<{ HookConfigSchema.parse(requiredHook); HookConfigSchema.parse(defaultHook); - writeYamlOrJson(config, { defaultIsm, defaultHook, requiredHook }); + writeYamlOrJson(configFilePath, { defaultIsm, defaultHook, requiredHook }); process.exit(0); }, @@ -102,10 +96,8 @@ export const config: CommandModuleWithContext<{ export const read: CommandModuleWithContext<{ chain: string; - ismAddress: string; - hookAddress: string; - ismOut: string; - hookOut: string; + mailbox: string; + config: string; }> = { command: 'read', describe: 'Reads onchain ISM & Hook configurations for given addresses', @@ -114,44 +106,26 @@ export const read: CommandModuleWithContext<{ ...chainCommandOption, demandOption: true, }, - ismAddress: addressCommandOption( - 'Address of the Interchain Security Module to read.', + mailbox: { + type: 'string', + description: 'Mailbox address used to derive the core config', + demandOption: true, + }, + config: outputFileCommandOption( + './configs/core-config.yaml', false, + 'The path to output a Core Config JSON or YAML file.', ), - hookAddress: addressCommandOption('Address of the Hook to read.', false), - ismOut: outputFileCommandOption(), - hookOut: outputFileCommandOption(), }, - handler: async ({ - context, - chain, - ismAddress, - hookAddress, - ismOut, - hookOut, - }) => { + handler: async ({ context, chain, mailbox, config: configFilePath }) => { logGray('Hyperlane Core Read'); logGray('-------------------'); - if (ismAddress) - await readIsmConfig({ - context, - chain, - address: ismAddress, - out: ismOut, - }); - if (hookAddress) - await readHookConfig({ - context, - chain, - address: hookAddress, - out: hookOut, - }); + const evmCoreReader = new EvmCoreReader(context.multiProvider, chain); + const coreConfig = await evmCoreReader.deriveCoreConfig(mailbox); + + writeYamlOrJson(configFilePath, coreConfig); - if (!ismAddress && !hookAddress) - warnYellow( - 'Must provide --ism-address, --hook-address, or both to read.', - ); process.exit(0); }, }; diff --git a/typescript/cli/src/config/ism.ts b/typescript/cli/src/config/ism.ts index bf49d22b6..d16926aba 100644 --- a/typescript/cli/src/config/ism.ts +++ b/typescript/cli/src/config/ism.ts @@ -58,10 +58,6 @@ export function readIsmConfig(filePath: string) { return parsedConfig; } -export function isValildIsmConfig(config: any) { - return IsmConfigSchema.safeParse(config).success; -} - const ISM_TYPE_DESCRIPTIONS: Record = { [IsmType.MESSAGE_ID_MULTISIG]: 'Validators need to sign just this messageId', [IsmType.MERKLE_ROOT_MULTISIG]: From 4bf7301eab2725d5004d2150c1fcad09f0db50b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Noah=20Bayindirli=20=F0=9F=A5=82?= Date: Tue, 4 Jun 2024 11:01:44 -0400 Subject: [PATCH 18/73] chore(sdk): add further zod support to SDK (#3834) ### Description * adds further zod support to SDK, namely for areas relating to `transactions` ### Drive-by changes * No ### Related issues - Fixes https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/3740 ### Backward compatibility - Yes ### Testing - None --- .changeset/yellow-donkeys-fetch.md | 5 + solidity/contracts/test/ERC20Test.sol | 12 +- typescript/cli/src/submit/submit.ts | 2 +- .../infra/src/govern/HyperlaneAppGovernor.ts | 8 +- typescript/sdk/src/index.ts | 200 ++++++++++-------- .../middleware/account/InterchainAccount.ts | 13 +- .../account/accounts.hardhat-test.ts | 4 +- .../sdk/src/middleware/account/schemas.ts | 13 ++ .../sdk/src/middleware/account/types.ts | 14 +- .../providers/transactions/schemas.test.ts | 72 +++++++ .../sdk/src/providers/transactions/schemas.ts | 12 +- .../submitter/TxSubmitterTypes.ts | 6 +- .../transactions/submitter/builder/schemas.ts | 11 + .../transactions/submitter/builder/types.ts | 5 + .../ethersV5/EV5GnosisSafeTxSubmitter.ts | 2 +- .../EV5ImpersonatedAccountTxSubmitter.ts | 4 +- .../submitter/ethersV5/EV5TxSubmitterTypes.ts | 12 -- .../submitter/ethersV5/schemas.test.ts | 61 ++++++ .../submitter/ethersV5/schemas.ts | 12 ++ .../transactions/submitter/ethersV5/types.ts | 13 ++ .../transactions/submitter/schemas.ts | 22 ++ .../providers/transactions/submitter/types.ts | 5 + .../transformer/TxTransformerTypes.ts | 2 +- .../EV5InterchainAccountTxTransformer.ts | 13 +- .../ethersV5/EV5TxTransformerTypes.ts | 4 - .../transformer/ethersV5/schemas.test.ts | 56 +++++ .../transformer/ethersV5/schemas.ts | 8 + .../transformer/ethersV5/types.ts | 7 + .../transactions/transformer/schemas.ts | 11 + .../transactions/transformer/types.ts | 5 + .../sdk/src/providers/transactions/types.ts | 4 +- typescript/sdk/src/utils/fork.ts | 2 +- 32 files changed, 473 insertions(+), 147 deletions(-) create mode 100644 .changeset/yellow-donkeys-fetch.md create mode 100644 typescript/sdk/src/providers/transactions/schemas.test.ts create mode 100644 typescript/sdk/src/providers/transactions/submitter/builder/schemas.ts create mode 100644 typescript/sdk/src/providers/transactions/submitter/builder/types.ts delete mode 100644 typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5TxSubmitterTypes.ts create mode 100644 typescript/sdk/src/providers/transactions/submitter/ethersV5/schemas.test.ts create mode 100644 typescript/sdk/src/providers/transactions/submitter/ethersV5/schemas.ts create mode 100644 typescript/sdk/src/providers/transactions/submitter/ethersV5/types.ts create mode 100644 typescript/sdk/src/providers/transactions/submitter/schemas.ts create mode 100644 typescript/sdk/src/providers/transactions/submitter/types.ts delete mode 100644 typescript/sdk/src/providers/transactions/transformer/ethersV5/EV5TxTransformerTypes.ts create mode 100644 typescript/sdk/src/providers/transactions/transformer/ethersV5/schemas.test.ts create mode 100644 typescript/sdk/src/providers/transactions/transformer/ethersV5/schemas.ts create mode 100644 typescript/sdk/src/providers/transactions/transformer/ethersV5/types.ts create mode 100644 typescript/sdk/src/providers/transactions/transformer/schemas.ts create mode 100644 typescript/sdk/src/providers/transactions/transformer/types.ts diff --git a/.changeset/yellow-donkeys-fetch.md b/.changeset/yellow-donkeys-fetch.md new file mode 100644 index 000000000..66bc128e8 --- /dev/null +++ b/.changeset/yellow-donkeys-fetch.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/sdk': minor +--- + +Adds further zod schema validation support throughout the SDK, namely for /transactions. diff --git a/solidity/contracts/test/ERC20Test.sol b/solidity/contracts/test/ERC20Test.sol index 8d4580c24..928a86051 100644 --- a/solidity/contracts/test/ERC20Test.sol +++ b/solidity/contracts/test/ERC20Test.sol @@ -67,14 +67,14 @@ contract XERC20Test is ERC20Test, IXERC20 { } function setLimits( - address _bridge, - uint256 _mintingLimit, - uint256 _burningLimit - ) external { - require(false); + address /* _bridge */, + uint256 /* _mintingLimit */, + uint256 /* _burningLimit */ + ) external pure { + require(false, "setLimits(): not implemented"); } - function owner() external returns (address) { + function owner() external pure returns (address) { return address(0x0); } } diff --git a/typescript/cli/src/submit/submit.ts b/typescript/cli/src/submit/submit.ts index cc344c2f0..fcde4afa4 100644 --- a/typescript/cli/src/submit/submit.ts +++ b/typescript/cli/src/submit/submit.ts @@ -75,7 +75,7 @@ async function getTransformer( transformerMetadata: TransformerMetadata, ): Promise> { switch (transformerMetadata.type) { - case TxTransformerType.ICA: + case TxTransformerType.INTERCHAIN_ACCOUNT: return new EV5InterchainAccountTxTransformer( multiProvider, transformerMetadata.props, diff --git a/typescript/infra/src/govern/HyperlaneAppGovernor.ts b/typescript/infra/src/govern/HyperlaneAppGovernor.ts index 187c1b700..82bf1530f 100644 --- a/typescript/infra/src/govern/HyperlaneAppGovernor.ts +++ b/typescript/infra/src/govern/HyperlaneAppGovernor.ts @@ -197,7 +197,13 @@ export abstract class HyperlaneAppGovernor< const callRemote = await this.interchainAccount.getCallRemote({ chain: origin, destination: chain, - innerCalls: [call], + innerCalls: [ + { + to: call.to, + data: call.data, + value: call.value?.toString() || '0', + }, + ], config: accountConfig, }); if (!callRemote.to || !callRemote.data) { diff --git a/typescript/sdk/src/index.ts b/typescript/sdk/src/index.ts index 5c09a2d06..3a1499986 100644 --- a/typescript/sdk/src/index.ts +++ b/typescript/sdk/src/index.ts @@ -24,6 +24,14 @@ export { testCosmosChain, testSealevelChain, } from './consts/testChains.js'; +export { + AddressesMap, + HyperlaneAddresses, + HyperlaneAddressesMap, + HyperlaneContracts, + HyperlaneContractsMap, + HyperlaneFactories, +} from './contracts/types.js'; export { attachContracts, attachContractsMap, @@ -38,13 +46,14 @@ export { serializeContractsMap, } from './contracts/contracts.js'; export { - AddressesMap, - HyperlaneAddresses, - HyperlaneAddressesMap, - HyperlaneContracts, - HyperlaneContractsMap, - HyperlaneFactories, -} from './contracts/types.js'; + CoreConfig, + CoreViolationType, + DispatchedMessage, + MailboxMultisigIsmViolation, + MailboxViolation, + MailboxViolationType, + ValidatorAnnounceViolation, +} from './core/types.js'; export { HyperlaneCore } from './core/HyperlaneCore.js'; export { HyperlaneCoreChecker } from './core/HyperlaneCoreChecker.js'; export { HyperlaneCoreDeployer } from './core/HyperlaneCoreDeployer.js'; @@ -55,10 +64,10 @@ export { TestRecipientConfig, TestRecipientDeployer, } from './core/TestRecipientDeployer.js'; +export { ICoreAdapter } from './core/adapters/types.js'; export { CosmWasmCoreAdapter } from './core/adapters/CosmWasmCoreAdapter.js'; export { EvmCoreAdapter } from './core/adapters/EvmCoreAdapter.js'; export { SealevelCoreAdapter } from './core/adapters/SealevelCoreAdapter.js'; -export { ICoreAdapter } from './core/adapters/types.js'; export { CoreAddresses, CoreFactories, @@ -66,28 +75,19 @@ export { } from './core/contracts.js'; export { HyperlaneLifecyleEvent } from './core/events.js'; export { EvmCoreReader } from './core/EvmCoreReader.js'; -export { - CoreConfig, - CoreViolationType, - DispatchedMessage, - MailboxMultisigIsmViolation, - MailboxViolation, - MailboxViolationType, - ValidatorAnnounceViolation, -} from './core/types.js'; export { CoreConfigSchema } from './core/schemas.js'; -export { HyperlaneAppChecker } from './deploy/HyperlaneAppChecker.js'; -export { - DeployerOptions, - HyperlaneDeployer, -} from './deploy/HyperlaneDeployer.js'; -export { HyperlaneProxyFactoryDeployer } from './deploy/HyperlaneProxyFactoryDeployer.js'; export { CheckerViolation, OwnableConfig, OwnerViolation, ViolationType, } from './deploy/types.js'; +export { HyperlaneAppChecker } from './deploy/HyperlaneAppChecker.js'; +export { + DeployerOptions, + HyperlaneDeployer, +} from './deploy/HyperlaneDeployer.js'; +export { HyperlaneProxyFactoryDeployer } from './deploy/HyperlaneProxyFactoryDeployer.js'; export { ContractVerifier } from './deploy/verify/ContractVerifier.js'; export { PostDeploymentContractVerifier } from './deploy/verify/PostDeploymentContractVerifier.js'; export { @@ -98,6 +98,14 @@ export { VerificationInput, } from './deploy/verify/types.js'; export * as verificationUtils from './deploy/verify/utils.js'; +export { + IgpBeneficiaryViolation, + IgpConfig, + IgpGasOraclesViolation, + IgpOverheadViolation, + IgpViolation, + IgpViolationType, +} from './gas/types.js'; export { HyperlaneIgp } from './gas/HyperlaneIgp.js'; export { HyperlaneIgpChecker } from './gas/HyperlaneIgpChecker.js'; export { HyperlaneIgpDeployer } from './gas/HyperlaneIgpDeployer.js'; @@ -112,16 +120,6 @@ export { export { IgpFactories, igpFactories } from './gas/contracts.js'; export { StorageGasOracleConfig } from './gas/oracle/types.js'; export { CoinGeckoTokenPriceGetter } from './gas/token-prices.js'; -export { - IgpBeneficiaryViolation, - IgpConfig, - IgpGasOraclesViolation, - IgpOverheadViolation, - IgpViolation, - IgpViolationType, -} from './gas/types.js'; -export { HyperlaneHookDeployer } from './hook/HyperlaneHookDeployer.js'; -export { EvmHookReader } from './hook/EvmHookReader.js'; export { AggregationHookConfig, DomainRoutingHookConfig, @@ -135,12 +133,8 @@ export { ProtocolFeeHookConfig, } from './hook/types.js'; export { HookConfigSchema } from './hook/schemas.js'; -export { HyperlaneIsmFactory } from './ism/HyperlaneIsmFactory.js'; -export { - buildAggregationIsmConfigs, - buildMultisigIsmConfigs, -} from './ism/multisig.js'; -export { EvmIsmReader } from './ism/EvmIsmReader.js'; +export { HyperlaneHookDeployer } from './hook/HyperlaneHookDeployer.js'; +export { EvmHookReader } from './hook/EvmHookReader.js'; export { AggregationIsmConfig, DeployedIsm, @@ -154,7 +148,30 @@ export { RoutingIsmConfig, TrustedRelayerIsmConfig, } from './ism/types.js'; +export { HyperlaneIsmFactory } from './ism/HyperlaneIsmFactory.js'; +export { + buildAggregationIsmConfigs, + buildMultisigIsmConfigs, +} from './ism/multisig.js'; +export { EvmIsmReader } from './ism/EvmIsmReader.js'; export { collectValidators, moduleCanCertainlyVerify } from './ism/utils.js'; +export { ZHash } from './metadata/customZodTypes.js'; +export { + BlockExplorer, + ChainMetadata, + ChainMetadataSchema, + ChainMetadataSchemaObject, + ChainTechnicalStack, + ExplorerFamily, + ExplorerFamilyValue, + NativeToken, + RpcUrl, + RpcUrlSchema, + getChainIdNumber, + getDomainId, + getReorgPeriod, + isValidChainMetadata, +} from './metadata/chainMetadataTypes.js'; export { ChainMetadataManager, ChainMetadataManagerOptions, @@ -180,23 +197,6 @@ export { ValidatorConfig, buildAgentConfig, } from './metadata/agentConfig.js'; -export { - BlockExplorer, - ChainMetadata, - ChainMetadataSchema, - ChainMetadataSchemaObject, - ChainTechnicalStack, - ExplorerFamily, - ExplorerFamilyValue, - NativeToken, - RpcUrl, - RpcUrlSchema, - getChainIdNumber, - getDomainId, - getReorgPeriod, - isValidChainMetadata, -} from './metadata/chainMetadataTypes.js'; -export { ZHash } from './metadata/customZodTypes.js'; export { HyperlaneDeploymentArtifacts, HyperlaneDeploymentArtifactsSchema, @@ -206,6 +206,14 @@ export { WarpRouteConfig, WarpRouteConfigSchema, } from './metadata/warpRouteConfig.js'; +export { + AccountConfigSchema, + GetCallRemoteSettingsSchema, +} from './middleware/account/schemas.js'; +export { + AccountConfig, + GetCallRemoteSettings, +} from './middleware/account/types.js'; export { InterchainAccount } from './middleware/account/InterchainAccount.js'; export { InterchainAccountChecker } from './middleware/account/InterchainAccountChecker.js'; export { @@ -216,7 +224,6 @@ export { InterchainAccountFactories, interchainAccountFactories, } from './middleware/account/contracts.js'; -export { AccountConfig } from './middleware/account/types.js'; export { LiquidityLayerApp } from './middleware/liquidity-layer/LiquidityLayerApp.js'; export { BridgeAdapterConfig, @@ -270,15 +277,6 @@ export { ViemTransaction, ViemTransactionReceipt, } from './providers/ProviderType.js'; -export { HyperlaneEtherscanProvider } from './providers/SmartProvider/HyperlaneEtherscanProvider.js'; -export { HyperlaneJsonRpcProvider } from './providers/SmartProvider/HyperlaneJsonRpcProvider.js'; -export { - AllProviderMethods, - IProviderMethods, - ProviderMethod, - excludeProviderMethods, -} from './providers/SmartProvider/ProviderMethods.js'; -export { HyperlaneSmartProvider } from './providers/SmartProvider/SmartProvider.js'; export { ChainMetadataWithRpcConnectionInfo, ProviderErrorResult, @@ -289,6 +287,15 @@ export { ProviderTimeoutResult, SmartProviderOptions, } from './providers/SmartProvider/types.js'; +export { HyperlaneEtherscanProvider } from './providers/SmartProvider/HyperlaneEtherscanProvider.js'; +export { HyperlaneJsonRpcProvider } from './providers/SmartProvider/HyperlaneJsonRpcProvider.js'; +export { + AllProviderMethods, + IProviderMethods, + ProviderMethod, + excludeProviderMethods, +} from './providers/SmartProvider/ProviderMethods.js'; +export { HyperlaneSmartProvider } from './providers/SmartProvider/SmartProvider.js'; export { ProviderBuilderFn, ProviderBuilderMap, @@ -301,22 +308,59 @@ export { defaultViemProviderBuilder, protocolToDefaultProviderBuilder, } from './providers/providerBuilders.js'; -export { TxSubmitterInterface } from './providers/transactions/submitter/TxSubmitterInterface.js'; +export { PopulatedTransactionSchema } from './providers/transactions/schemas.js'; +export { + CallData, + PopulatedTransaction, +} from './providers/transactions/types.js'; + export { TxSubmitterType } from './providers/transactions/submitter/TxSubmitterTypes.js'; +export { SubmitterMetadataSchema } from './providers/transactions/submitter/schemas.js'; +export { SubmitterMetadata } from './providers/transactions/submitter/types.js'; +export { TxSubmitterInterface } from './providers/transactions/submitter/TxSubmitterInterface.js'; + +export { + EV5GnosisSafeTxSubmitterPropsSchema, + EV5ImpersonatedAccountTxSubmitterPropsSchema, +} from './providers/transactions/submitter/ethersV5/schemas.js'; export { EV5GnosisSafeTxSubmitterProps, EV5ImpersonatedAccountTxSubmitterProps, -} from './providers/transactions/submitter/ethersV5/EV5TxSubmitterTypes.js'; +} from './providers/transactions/submitter/ethersV5/types.js'; + +export { SubmissionStrategySchema } from './providers/transactions/submitter/builder/schemas.js'; +export { SubmissionStrategy } from './providers/transactions/submitter/builder/types.js'; export { TxSubmitterBuilder } from './providers/transactions/submitter/builder/TxSubmitterBuilder.js'; + export { EV5GnosisSafeTxSubmitter } from './providers/transactions/submitter/ethersV5/EV5GnosisSafeTxSubmitter.js'; export { EV5ImpersonatedAccountTxSubmitter } from './providers/transactions/submitter/ethersV5/EV5ImpersonatedAccountTxSubmitter.js'; export { EV5JsonRpcTxSubmitter } from './providers/transactions/submitter/ethersV5/EV5JsonRpcTxSubmitter.js'; export { EV5TxSubmitterInterface } from './providers/transactions/submitter/ethersV5/EV5TxSubmitterInterface.js'; -export { TxTransformerInterface } from './providers/transactions/transformer/TxTransformerInterface.js'; + export { TxTransformerType } from './providers/transactions/transformer/TxTransformerTypes.js'; -export { EV5InterchainAccountTxTransformerProps } from './providers/transactions/transformer/ethersV5/EV5TxTransformerTypes.js'; +export { TransformerMetadataSchema } from './providers/transactions/transformer/schemas.js'; +export { TransformerMetadata } from './providers/transactions/transformer/types.js'; +export { TxTransformerInterface } from './providers/transactions/transformer/TxTransformerInterface.js'; + +export { EV5InterchainAccountTxTransformerPropsSchema } from './providers/transactions/transformer/ethersV5/schemas.js'; +export { EV5InterchainAccountTxTransformerProps } from './providers/transactions/transformer/ethersV5/types.js'; export { EV5InterchainAccountTxTransformer } from './providers/transactions/transformer/ethersV5/EV5InterchainAccountTxTransformer.js'; export { EV5TxTransformerInterface } from './providers/transactions/transformer/ethersV5/EV5TxTransformerInterface.js'; + +export { + MailboxClientConfig as ConnectionClientConfig, + ClientViolation as ConnectionClientViolation, + ClientViolationType as ConnectionClientViolationType, + GasRouterConfig, + MailboxClientConfig, + ProxiedFactories, + ProxiedRouterConfig, + RouterAddress, + RouterConfig, + RouterViolation, + RouterViolationType, + proxiedFactories, +} from './router/types.js'; export { GasRouterDeployer } from './router/GasRouterDeployer.js'; export { HyperlaneRouterChecker } from './router/HyperlaneRouterChecker.js'; export { HyperlaneRouterDeployer } from './router/HyperlaneRouterDeployer.js'; @@ -325,6 +369,7 @@ export { MultiProtocolRouterApp, } from './router/MultiProtocolRouterApps.js'; export { GasRouterApp, RouterApp } from './router/RouterApps.js'; +export { IGasRouterAdapter, IRouterAdapter } from './router/adapters/types.js'; export { EvmGasRouterAdapter, EvmRouterAdapter, @@ -333,21 +378,6 @@ export { SealevelGasRouterAdapter, SealevelRouterAdapter, } from './router/adapters/SealevelRouterAdapter.js'; -export { IGasRouterAdapter, IRouterAdapter } from './router/adapters/types.js'; -export { - MailboxClientConfig as ConnectionClientConfig, - ClientViolation as ConnectionClientViolation, - ClientViolationType as ConnectionClientViolationType, - GasRouterConfig, - MailboxClientConfig, - ProxiedFactories, - ProxiedRouterConfig, - RouterAddress, - RouterConfig, - RouterViolation, - RouterViolationType, - proxiedFactories, -} from './router/types.js'; export { IToken, TokenArgs, TokenConfigSchema } from './token/IToken.js'; export { Token } from './token/Token.js'; export { TokenAmount } from './token/TokenAmount.js'; diff --git a/typescript/sdk/src/middleware/account/InterchainAccount.ts b/typescript/sdk/src/middleware/account/InterchainAccount.ts index f1c559ad5..7c5fe6c0a 100644 --- a/typescript/sdk/src/middleware/account/InterchainAccount.ts +++ b/typescript/sdk/src/middleware/account/InterchainAccount.ts @@ -1,9 +1,8 @@ -import { BigNumber, BytesLike, PopulatedTransaction } from 'ethers'; +import { BigNumber, PopulatedTransaction } from 'ethers'; import { InterchainAccountRouter } from '@hyperlane-xyz/core'; import { Address, - CallData, addressToBytes32, bytes32ToAddress, } from '@hyperlane-xyz/utils'; @@ -22,15 +21,7 @@ import { InterchainAccountFactories, interchainAccountFactories, } from './contracts.js'; -import { AccountConfig } from './types.js'; - -export interface GetCallRemoteSettings { - chain: ChainName; - destination: ChainName; - innerCalls: CallData[]; - config: AccountConfig; - hookMetadata?: BytesLike; -} +import { AccountConfig, GetCallRemoteSettings } from './types.js'; export class InterchainAccount extends RouterApp { constructor( diff --git a/typescript/sdk/src/middleware/account/accounts.hardhat-test.ts b/typescript/sdk/src/middleware/account/accounts.hardhat-test.ts index 323f9af53..78cb86fbd 100644 --- a/typescript/sdk/src/middleware/account/accounts.hardhat-test.ts +++ b/typescript/sdk/src/middleware/account/accounts.hardhat-test.ts @@ -1,6 +1,6 @@ import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers.js'; import { expect } from 'chai'; -import { BigNumber, constants } from 'ethers'; +import { constants } from 'ethers'; import hre from 'hardhat'; import { @@ -84,7 +84,7 @@ describe('InterchainAccounts', async () => { const call = { to: recipient.address, data, - value: BigNumber.from('0'), + value: '0', }; const quote = await local['quoteGasPayment(uint32)']( multiProvider.getDomainId(remoteChain), diff --git a/typescript/sdk/src/middleware/account/schemas.ts b/typescript/sdk/src/middleware/account/schemas.ts index 5b95b13d1..6e90b1a19 100644 --- a/typescript/sdk/src/middleware/account/schemas.ts +++ b/typescript/sdk/src/middleware/account/schemas.ts @@ -1,6 +1,10 @@ import { z } from 'zod'; import { ZChainName, ZHash } from '../../metadata/customZodTypes.js'; +import { + BigNumberSchema, + CallDataSchema, +} from '../../providers/transactions/schemas.js'; export const AccountConfigSchema = z.object({ origin: ZChainName, @@ -9,3 +13,12 @@ export const AccountConfigSchema = z.object({ routerOverride: ZHash.optional(), ismOverride: ZHash.optional(), }); + +/* For InterchainAccount::getCallRemote() */ +export const GetCallRemoteSettingsSchema = z.object({ + chain: ZChainName, + destination: ZChainName, + innerCalls: z.array(CallDataSchema), + config: AccountConfigSchema, + hookMetadata: BigNumberSchema.optional(), +}); diff --git a/typescript/sdk/src/middleware/account/types.ts b/typescript/sdk/src/middleware/account/types.ts index 85d5acda7..58dd28d14 100644 --- a/typescript/sdk/src/middleware/account/types.ts +++ b/typescript/sdk/src/middleware/account/types.ts @@ -1,11 +1,7 @@ -import { Address } from '@hyperlane-xyz/utils'; +import { z } from 'zod'; -import { ChainName } from '../../types.js'; +import { AccountConfigSchema, GetCallRemoteSettingsSchema } from './schemas.js'; -export type AccountConfig = { - origin: ChainName; - owner: Address; - localRouter?: Address; - routerOverride?: Address; - ismOverride?: Address; -}; +export type AccountConfig = z.infer; +/* For InterchainAccount::getCallRemote() */ +export type GetCallRemoteSettings = z.infer; diff --git a/typescript/sdk/src/providers/transactions/schemas.test.ts b/typescript/sdk/src/providers/transactions/schemas.test.ts new file mode 100644 index 000000000..248878d30 --- /dev/null +++ b/typescript/sdk/src/providers/transactions/schemas.test.ts @@ -0,0 +1,72 @@ +import { expect } from 'chai'; + +import { Address } from '@hyperlane-xyz/utils'; + +import { CallDataSchema, PopulatedTransactionSchema } from './schemas.js'; +import { CallData, PopulatedTransaction } from './types.js'; + +describe('transactions schemas', () => { + const ADDRESS_MOCK: Address = '0x1234567890123456789012345678901234567890'; + const DATA_MOCK: string = '0xabcdef'; + const CHAIN_ID_MOCK: number = 1; + const VALUE_MOCK: string = '100'; + + const INVALID_ADDRESS: Address = '0x1'; + + describe('PopulatedTransactionSchema', () => { + it('should parse valid PopulatedTransaction', () => { + const validPopulatedTransaction: PopulatedTransaction = { + to: ADDRESS_MOCK, + data: DATA_MOCK, + chainId: CHAIN_ID_MOCK, + }; + const result = PopulatedTransactionSchema.safeParse( + validPopulatedTransaction, + ); + expect(result.success).to.be.true; + }); + + it('should fail parsing invalid PopulatedTransaction', () => { + const invalidPopulatedTransaction: PopulatedTransaction = { + to: INVALID_ADDRESS, + data: DATA_MOCK, + chainId: CHAIN_ID_MOCK, + }; + const result = PopulatedTransactionSchema.safeParse( + invalidPopulatedTransaction, + ); + expect(result.success).to.be.false; + }); + }); + + describe('CallDataSchema', () => { + it('should parse valid CallData', () => { + const validCallData: CallData = { + to: ADDRESS_MOCK, + data: DATA_MOCK, + value: VALUE_MOCK, + }; + const result = CallDataSchema.safeParse(validCallData); + expect(result.success).to.be.true; + }); + + it('should parse CallData without optional value', () => { + const validCallDataWithoutValue: CallData = { + to: ADDRESS_MOCK, + data: DATA_MOCK, + }; + const result = CallDataSchema.safeParse(validCallDataWithoutValue); + expect(result.success).to.be.true; + }); + + it('should fail parsing invalid CallData', () => { + const invalidCallData: CallData = { + to: INVALID_ADDRESS, + data: DATA_MOCK, + value: VALUE_MOCK, + }; + const result = CallDataSchema.safeParse(invalidCallData); + expect(result.success).to.be.false; + }); + }); +}); diff --git a/typescript/sdk/src/providers/transactions/schemas.ts b/typescript/sdk/src/providers/transactions/schemas.ts index da03743ea..706a96179 100644 --- a/typescript/sdk/src/providers/transactions/schemas.ts +++ b/typescript/sdk/src/providers/transactions/schemas.ts @@ -1,7 +1,17 @@ import { z } from 'zod'; +import { ZHash } from '../../metadata/customZodTypes.js'; + +export const BigNumberSchema = z.string(); + export const PopulatedTransactionSchema = z.object({ - to: z.string(), + to: ZHash, data: z.string(), chainId: z.number(), }); + +export const CallDataSchema = z.object({ + to: ZHash, + data: z.string(), + value: BigNumberSchema.optional(), +}); diff --git a/typescript/sdk/src/providers/transactions/submitter/TxSubmitterTypes.ts b/typescript/sdk/src/providers/transactions/submitter/TxSubmitterTypes.ts index 4e38f9c25..7b3b47840 100644 --- a/typescript/sdk/src/providers/transactions/submitter/TxSubmitterTypes.ts +++ b/typescript/sdk/src/providers/transactions/submitter/TxSubmitterTypes.ts @@ -1,5 +1,5 @@ export enum TxSubmitterType { - JSON_RPC = 'JSON RPC', - IMPERSONATED_ACCOUNT = 'Impersonated Account', - GNOSIS_SAFE = 'Gnosis Safe', + JSON_RPC = 'jsonRpc', + IMPERSONATED_ACCOUNT = 'impersonatedAccount', + GNOSIS_SAFE = 'gnosisSafe', } diff --git a/typescript/sdk/src/providers/transactions/submitter/builder/schemas.ts b/typescript/sdk/src/providers/transactions/submitter/builder/schemas.ts new file mode 100644 index 000000000..0ccea2827 --- /dev/null +++ b/typescript/sdk/src/providers/transactions/submitter/builder/schemas.ts @@ -0,0 +1,11 @@ +import { z } from 'zod'; + +import { ZChainName } from '../../../../metadata/customZodTypes.js'; +import { TransformerMetadataSchema } from '../../transformer/schemas.js'; +import { SubmitterMetadataSchema } from '../schemas.js'; + +export const SubmissionStrategySchema = z.object({ + chain: ZChainName, + submitter: SubmitterMetadataSchema, + transforms: z.array(TransformerMetadataSchema).optional(), +}); diff --git a/typescript/sdk/src/providers/transactions/submitter/builder/types.ts b/typescript/sdk/src/providers/transactions/submitter/builder/types.ts new file mode 100644 index 000000000..e8a901aca --- /dev/null +++ b/typescript/sdk/src/providers/transactions/submitter/builder/types.ts @@ -0,0 +1,5 @@ +import { z } from 'zod'; + +import { SubmissionStrategySchema } from './schemas.js'; + +export type SubmissionStrategy = z.infer; diff --git a/typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5GnosisSafeTxSubmitter.ts b/typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5GnosisSafeTxSubmitter.ts index e04622a33..74e1ce53c 100644 --- a/typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5GnosisSafeTxSubmitter.ts +++ b/typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5GnosisSafeTxSubmitter.ts @@ -9,7 +9,7 @@ import { PopulatedTransaction } from '../../types.js'; import { TxSubmitterType } from '../TxSubmitterTypes.js'; import { EV5TxSubmitterInterface } from './EV5TxSubmitterInterface.js'; -import { EV5GnosisSafeTxSubmitterProps } from './EV5TxSubmitterTypes.js'; +import { EV5GnosisSafeTxSubmitterProps } from './types.js'; export class EV5GnosisSafeTxSubmitter implements EV5TxSubmitterInterface { public readonly txSubmitterType: TxSubmitterType = diff --git a/typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5ImpersonatedAccountTxSubmitter.ts b/typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5ImpersonatedAccountTxSubmitter.ts index 98ca608b1..c184ab17d 100644 --- a/typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5ImpersonatedAccountTxSubmitter.ts +++ b/typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5ImpersonatedAccountTxSubmitter.ts @@ -12,7 +12,7 @@ import { MultiProvider } from '../../../MultiProvider.js'; import { TxSubmitterType } from '../TxSubmitterTypes.js'; import { EV5JsonRpcTxSubmitter } from './EV5JsonRpcTxSubmitter.js'; -import { EV5ImpersonatedAccountTxSubmitterProps } from './EV5TxSubmitterTypes.js'; +import { EV5ImpersonatedAccountTxSubmitterProps } from './types.js'; export class EV5ImpersonatedAccountTxSubmitter extends EV5JsonRpcTxSubmitter { public readonly txSubmitterType: TxSubmitterType = @@ -35,7 +35,7 @@ export class EV5ImpersonatedAccountTxSubmitter extends EV5JsonRpcTxSubmitter { const impersonatedAccount = await impersonateAccount( this.props.userAddress, ); - super.multiProvider.setSharedSigner(impersonatedAccount); + this.multiProvider.setSharedSigner(impersonatedAccount); const transactionReceipts = await super.submit(...txs); await stopImpersonatingAccount(this.props.userAddress); return transactionReceipts; diff --git a/typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5TxSubmitterTypes.ts b/typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5TxSubmitterTypes.ts deleted file mode 100644 index b09deb1a6..000000000 --- a/typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5TxSubmitterTypes.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Address } from '@hyperlane-xyz/utils'; - -import { ChainName } from '../../../../types.js'; - -export interface EV5GnosisSafeTxSubmitterProps { - chain: ChainName; - safeAddress: Address; -} - -export interface EV5ImpersonatedAccountTxSubmitterProps { - userAddress: Address; -} diff --git a/typescript/sdk/src/providers/transactions/submitter/ethersV5/schemas.test.ts b/typescript/sdk/src/providers/transactions/submitter/ethersV5/schemas.test.ts new file mode 100644 index 000000000..fdffb778a --- /dev/null +++ b/typescript/sdk/src/providers/transactions/submitter/ethersV5/schemas.test.ts @@ -0,0 +1,61 @@ +import { expect } from 'chai'; + +import { Address } from '@hyperlane-xyz/utils'; + +import { ChainName } from '../../../../types.js'; + +import { + EV5GnosisSafeTxSubmitterPropsSchema, + EV5ImpersonatedAccountTxSubmitterPropsSchema, +} from './schemas.js'; +import { + EV5GnosisSafeTxSubmitterProps, + EV5ImpersonatedAccountTxSubmitterProps, +} from './types.js'; + +describe('ethersV5 submitter props schemas', () => { + const CHAIN_MOCK: ChainName = 'ethereum'; + const ADDRESS_MOCK: Address = '0x1234567890123456789012345678901234567890'; + + const INVALID_ADDRESS: Address = '0x1'; + + describe('EV5GnosisSafeTxSubmitterPropsSchema', () => { + it('should parse valid props', () => { + const validProps: EV5GnosisSafeTxSubmitterProps = { + chain: CHAIN_MOCK, + safeAddress: ADDRESS_MOCK, + }; + const result = EV5GnosisSafeTxSubmitterPropsSchema.safeParse(validProps); + expect(result.success).to.be.true; + }); + + it('should fail parsing invalid props', () => { + const invalidProps = { + chain: CHAIN_MOCK, + }; + const result = + EV5GnosisSafeTxSubmitterPropsSchema.safeParse(invalidProps); + expect(result.success).to.be.false; + }); + }); + + describe('EV5ImpersonatedAccountTxSubmitterPropsSchema', () => { + it('should parse valid props', () => { + const validProps: EV5ImpersonatedAccountTxSubmitterProps = { + userAddress: '0x1234567890123456789012345678901234567890', + }; + const result = + EV5ImpersonatedAccountTxSubmitterPropsSchema.safeParse(validProps); + expect(result.success).to.be.true; + }); + + it('should fail parsing invalid props', () => { + const invalidProps: EV5ImpersonatedAccountTxSubmitterProps = { + userAddress: INVALID_ADDRESS, + }; + const result = + EV5ImpersonatedAccountTxSubmitterPropsSchema.safeParse(invalidProps); + expect(result.success).to.be.false; + }); + }); +}); diff --git a/typescript/sdk/src/providers/transactions/submitter/ethersV5/schemas.ts b/typescript/sdk/src/providers/transactions/submitter/ethersV5/schemas.ts new file mode 100644 index 000000000..7b5cb9a6c --- /dev/null +++ b/typescript/sdk/src/providers/transactions/submitter/ethersV5/schemas.ts @@ -0,0 +1,12 @@ +import { z } from 'zod'; + +import { ZChainName, ZHash } from '../../../../metadata/customZodTypes.js'; + +export const EV5GnosisSafeTxSubmitterPropsSchema = z.object({ + chain: ZChainName, + safeAddress: ZHash, +}); + +export const EV5ImpersonatedAccountTxSubmitterPropsSchema = z.object({ + userAddress: ZHash, +}); diff --git a/typescript/sdk/src/providers/transactions/submitter/ethersV5/types.ts b/typescript/sdk/src/providers/transactions/submitter/ethersV5/types.ts new file mode 100644 index 000000000..67edd01d4 --- /dev/null +++ b/typescript/sdk/src/providers/transactions/submitter/ethersV5/types.ts @@ -0,0 +1,13 @@ +import { z } from 'zod'; + +import { + EV5GnosisSafeTxSubmitterPropsSchema, + EV5ImpersonatedAccountTxSubmitterPropsSchema, +} from './schemas.js'; + +export type EV5GnosisSafeTxSubmitterProps = z.infer< + typeof EV5GnosisSafeTxSubmitterPropsSchema +>; +export type EV5ImpersonatedAccountTxSubmitterProps = z.infer< + typeof EV5ImpersonatedAccountTxSubmitterPropsSchema +>; diff --git a/typescript/sdk/src/providers/transactions/submitter/schemas.ts b/typescript/sdk/src/providers/transactions/submitter/schemas.ts new file mode 100644 index 000000000..0c4185aaf --- /dev/null +++ b/typescript/sdk/src/providers/transactions/submitter/schemas.ts @@ -0,0 +1,22 @@ +import { z } from 'zod'; + +import { TxSubmitterType } from './TxSubmitterTypes.js'; +import { + EV5GnosisSafeTxSubmitterPropsSchema, + EV5ImpersonatedAccountTxSubmitterPropsSchema, +} from './ethersV5/schemas.js'; + +export const SubmitterMetadataSchema = z.discriminatedUnion('type', [ + z.object({ + type: z.literal(TxSubmitterType.JSON_RPC), + props: z.object({}).optional(), + }), + z.object({ + type: z.literal(TxSubmitterType.IMPERSONATED_ACCOUNT), + props: EV5ImpersonatedAccountTxSubmitterPropsSchema, + }), + z.object({ + type: z.literal(TxSubmitterType.GNOSIS_SAFE), + props: EV5GnosisSafeTxSubmitterPropsSchema, + }), +]); diff --git a/typescript/sdk/src/providers/transactions/submitter/types.ts b/typescript/sdk/src/providers/transactions/submitter/types.ts new file mode 100644 index 000000000..92e79f9b4 --- /dev/null +++ b/typescript/sdk/src/providers/transactions/submitter/types.ts @@ -0,0 +1,5 @@ +import { z } from 'zod'; + +import { SubmitterMetadataSchema } from './schemas.js'; + +export type SubmitterMetadata = z.infer; diff --git a/typescript/sdk/src/providers/transactions/transformer/TxTransformerTypes.ts b/typescript/sdk/src/providers/transactions/transformer/TxTransformerTypes.ts index b8e029b2c..0f04841ef 100644 --- a/typescript/sdk/src/providers/transactions/transformer/TxTransformerTypes.ts +++ b/typescript/sdk/src/providers/transactions/transformer/TxTransformerTypes.ts @@ -1,3 +1,3 @@ export enum TxTransformerType { - ICA = 'Interchain Account', + INTERCHAIN_ACCOUNT = 'interchainAccount', } diff --git a/typescript/sdk/src/providers/transactions/transformer/ethersV5/EV5InterchainAccountTxTransformer.ts b/typescript/sdk/src/providers/transactions/transformer/ethersV5/EV5InterchainAccountTxTransformer.ts index 59dcc8edc..9a3e659fb 100644 --- a/typescript/sdk/src/providers/transactions/transformer/ethersV5/EV5InterchainAccountTxTransformer.ts +++ b/typescript/sdk/src/providers/transactions/transformer/ethersV5/EV5InterchainAccountTxTransformer.ts @@ -1,7 +1,7 @@ import { ethers } from 'ethers'; import { Logger } from 'pino'; -import { CallData, assert, objKeys, rootLogger } from '@hyperlane-xyz/utils'; +import { assert, objKeys, rootLogger } from '@hyperlane-xyz/utils'; import { InterchainAccount, @@ -9,16 +9,17 @@ import { } from '../../../../middleware/account/InterchainAccount.js'; import { ChainName } from '../../../../types.js'; import { MultiProvider } from '../../../MultiProvider.js'; -import { PopulatedTransaction } from '../../types.js'; +import { CallData, PopulatedTransaction } from '../../types.js'; import { TxTransformerType } from '../TxTransformerTypes.js'; import { EV5TxTransformerInterface } from './EV5TxTransformerInterface.js'; -import { EV5InterchainAccountTxTransformerProps } from './EV5TxTransformerTypes.js'; +import { EV5InterchainAccountTxTransformerProps } from './types.js'; export class EV5InterchainAccountTxTransformer implements EV5TxTransformerInterface { - public readonly txTransformerType: TxTransformerType = TxTransformerType.ICA; + public readonly txTransformerType: TxTransformerType = + TxTransformerType.INTERCHAIN_ACCOUNT; protected readonly logger: Logger = rootLogger.child({ module: 'ica-transformer', }); @@ -39,11 +40,11 @@ export class EV5InterchainAccountTxTransformer const txChainsToInnerCalls: Record = txs.reduce( ( txChainToInnerCalls: Record, - { to, data, value, chainId }: PopulatedTransaction, + { to, data, chainId }: PopulatedTransaction, ) => { const txChain = this.multiProvider.getChainName(chainId); txChainToInnerCalls[txChain] ||= []; - txChainToInnerCalls[txChain].push({ to, data, value }); + txChainToInnerCalls[txChain].push({ to, data }); return txChainToInnerCalls; }, {}, diff --git a/typescript/sdk/src/providers/transactions/transformer/ethersV5/EV5TxTransformerTypes.ts b/typescript/sdk/src/providers/transactions/transformer/ethersV5/EV5TxTransformerTypes.ts deleted file mode 100644 index ed786ab79..000000000 --- a/typescript/sdk/src/providers/transactions/transformer/ethersV5/EV5TxTransformerTypes.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { GetCallRemoteSettings } from '../../../../middleware/account/InterchainAccount.js'; - -export interface EV5InterchainAccountTxTransformerProps - extends Omit {} diff --git a/typescript/sdk/src/providers/transactions/transformer/ethersV5/schemas.test.ts b/typescript/sdk/src/providers/transactions/transformer/ethersV5/schemas.test.ts new file mode 100644 index 000000000..5418f31fe --- /dev/null +++ b/typescript/sdk/src/providers/transactions/transformer/ethersV5/schemas.test.ts @@ -0,0 +1,56 @@ +import { expect } from 'chai'; + +import { Address } from '@hyperlane-xyz/utils'; + +import { ChainName } from '../../../../types.js'; + +import { EV5InterchainAccountTxTransformerPropsSchema } from './schemas.js'; +import { EV5InterchainAccountTxTransformerProps } from './types.js'; + +describe('ethersV5 transformer props schemas', () => { + const CHAIN_MOCK: ChainName = 'ethereum'; + const ORIGIN_MOCK: ChainName = 'arbitrum'; + const ADDRESS_MOCK: Address = '0x1234567890123456789012345678901234567890'; + const HOOK_METADATA_MOCK: string = '1243'; + + describe('EV5InterchainAccountTxTransformerProps', () => { + it('should parse valid props', () => { + const validProps: EV5InterchainAccountTxTransformerProps = { + chain: CHAIN_MOCK, + config: { + origin: ORIGIN_MOCK, + owner: ADDRESS_MOCK, + }, + hookMetadata: HOOK_METADATA_MOCK, + }; + const result = + EV5InterchainAccountTxTransformerPropsSchema.safeParse(validProps); + expect(result.success).to.be.true; + }); + + it('should fail parsing props when required fields are missing', () => { + const invalidProps = { + chain: CHAIN_MOCK, + }; + const result = + EV5InterchainAccountTxTransformerPropsSchema.safeParse(invalidProps); + expect(result.success).to.be.false; + }); + + it('should parse props when extra fields are present', () => { + const validProps = { + chain: CHAIN_MOCK, + config: { + origin: ORIGIN_MOCK, + owner: ADDRESS_MOCK, + }, + miscData: 1234, + nonsense: 'bleh', + ish: true, + }; + const result = + EV5InterchainAccountTxTransformerPropsSchema.safeParse(validProps); + expect(result.success).to.be.true; + }); + }); +}); diff --git a/typescript/sdk/src/providers/transactions/transformer/ethersV5/schemas.ts b/typescript/sdk/src/providers/transactions/transformer/ethersV5/schemas.ts new file mode 100644 index 000000000..7d49b0e91 --- /dev/null +++ b/typescript/sdk/src/providers/transactions/transformer/ethersV5/schemas.ts @@ -0,0 +1,8 @@ +import { GetCallRemoteSettingsSchema } from '../../../../middleware/account/schemas.js'; + +export const EV5InterchainAccountTxTransformerPropsSchema = + GetCallRemoteSettingsSchema.pick({ + chain: true, + config: true, + hookMetadata: true, + }); diff --git a/typescript/sdk/src/providers/transactions/transformer/ethersV5/types.ts b/typescript/sdk/src/providers/transactions/transformer/ethersV5/types.ts new file mode 100644 index 000000000..f4b848f42 --- /dev/null +++ b/typescript/sdk/src/providers/transactions/transformer/ethersV5/types.ts @@ -0,0 +1,7 @@ +import { z } from 'zod'; + +import { EV5InterchainAccountTxTransformerPropsSchema } from './schemas.js'; + +export type EV5InterchainAccountTxTransformerProps = z.infer< + typeof EV5InterchainAccountTxTransformerPropsSchema +>; diff --git a/typescript/sdk/src/providers/transactions/transformer/schemas.ts b/typescript/sdk/src/providers/transactions/transformer/schemas.ts new file mode 100644 index 000000000..14a5bb358 --- /dev/null +++ b/typescript/sdk/src/providers/transactions/transformer/schemas.ts @@ -0,0 +1,11 @@ +import { z } from 'zod'; + +import { TxTransformerType } from './TxTransformerTypes.js'; +import { EV5InterchainAccountTxTransformerPropsSchema } from './ethersV5/schemas.js'; + +export const TransformerMetadataSchema = z.discriminatedUnion('type', [ + z.object({ + type: z.literal(TxTransformerType.INTERCHAIN_ACCOUNT), + props: EV5InterchainAccountTxTransformerPropsSchema, + }), +]); diff --git a/typescript/sdk/src/providers/transactions/transformer/types.ts b/typescript/sdk/src/providers/transactions/transformer/types.ts new file mode 100644 index 000000000..7f5e47912 --- /dev/null +++ b/typescript/sdk/src/providers/transactions/transformer/types.ts @@ -0,0 +1,5 @@ +import { z } from 'zod'; + +import { TransformerMetadataSchema } from './schemas.js'; + +export type TransformerMetadata = z.infer; diff --git a/typescript/sdk/src/providers/transactions/types.ts b/typescript/sdk/src/providers/transactions/types.ts index 2c53d65a3..b1b91b1c4 100644 --- a/typescript/sdk/src/providers/transactions/types.ts +++ b/typescript/sdk/src/providers/transactions/types.ts @@ -1,7 +1,9 @@ import { ethers } from 'ethers'; import { z } from 'zod'; -import { PopulatedTransactionSchema } from './schemas.js'; +import { CallDataSchema, PopulatedTransactionSchema } from './schemas.js'; export type PopulatedTransaction = z.infer & ethers.PopulatedTransaction; + +export type CallData = z.infer; diff --git a/typescript/sdk/src/utils/fork.ts b/typescript/sdk/src/utils/fork.ts index 51ed576e0..7215133ae 100644 --- a/typescript/sdk/src/utils/fork.ts +++ b/typescript/sdk/src/utils/fork.ts @@ -93,7 +93,7 @@ export const stopImpersonatingAccount = async ( ) => { rootLogger.info(`Stopping account impersonation for address (${address})...`); - if (isValidAddressEvm(address)) + if (!isValidAddressEvm(address)) throw new Error( `Cannot stop account impersonation: invalid address format: ${address}`, ); From ba4c9a7dabad887c02f794e264d295b877c579b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Noah=20Bayindirli=20=F0=9F=A5=82?= Date: Tue, 4 Jun 2024 12:32:04 -0400 Subject: [PATCH 19/73] feat(cli): add 'hyperlane warp send' command (#3884) ### Description - adds `hl warp send` command in favor of `hl send transfer` ### Drive-by changes * none ### Related issues - fixes https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/3691 ### Backward compatibility - yes ### Testing - ci-test: includes call to `hl warp send` - manual: `hl warp send` --- .changeset/spotty-days-worry.md | 5 +++ typescript/cli/ci-test.sh | 2 +- typescript/cli/src/commands/send.ts | 63 +--------------------------- typescript/cli/src/commands/warp.ts | 64 ++++++++++++++++++++++++++++- 4 files changed, 70 insertions(+), 64 deletions(-) create mode 100644 .changeset/spotty-days-worry.md diff --git a/.changeset/spotty-days-worry.md b/.changeset/spotty-days-worry.md new file mode 100644 index 000000000..cee84d1d3 --- /dev/null +++ b/.changeset/spotty-days-worry.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/cli': minor +--- + +Add warp send in favor of send transfer. diff --git a/typescript/cli/ci-test.sh b/typescript/cli/ci-test.sh index 58019fdbb..601a56c6c 100755 --- a/typescript/cli/ci-test.sh +++ b/typescript/cli/ci-test.sh @@ -245,7 +245,7 @@ run_hyperlane_send_message() { WARP_CONFIG_FILE="$REGISTRY_PATH/deployments/warp_routes/FAKE/${CHAIN1}-${CHAIN2}-config.yaml" echo -e "\nSending test warp transfer" - yarn workspace @hyperlane-xyz/cli run hyperlane send transfer \ + yarn workspace @hyperlane-xyz/cli run hyperlane warp send \ --registry $REGISTRY_PATH \ --overrides " " \ --origin ${CHAIN1} \ diff --git a/typescript/cli/src/commands/send.ts b/typescript/cli/src/commands/send.ts index e77a0998c..1167b3b55 100644 --- a/typescript/cli/src/commands/send.ts +++ b/typescript/cli/src/commands/send.ts @@ -4,22 +4,15 @@ import { CommandModule, Options } from 'yargs'; import { CommandModuleWithWriteContext } from '../context/types.js'; import { log } from '../logger.js'; import { sendTestMessage } from '../send/message.js'; -import { sendTestTransfer } from '../send/transfer.js'; - -import { warpCoreConfigCommandOption } from './options.js'; /** * Parent command */ export const sendCommand: CommandModule = { command: 'send', - describe: 'Send a test message or transfer', + describe: 'Send a test message', builder: (yargs) => - yargs - .command(messageCommand) - .command(transferCommand) - .version(false) - .demandCommand(), + yargs.command(messageCommand).version(false).demandCommand(), handler: () => log('Command required'), }; @@ -94,55 +87,3 @@ const messageCommand: CommandModuleWithWriteContext< process.exit(0); }, }; - -/** - * Transfer command - */ -const transferCommand: CommandModuleWithWriteContext< - MessageOptionsArgTypes & { - warp: string; - router?: string; - wei: string; - recipient?: string; - } -> = { - command: 'transfer', - describe: 'Send a test token transfer on a warp route', - builder: { - ...messageOptions, - warp: warpCoreConfigCommandOption, - wei: { - type: 'string', - description: 'Amount in wei to send', - default: 1, - }, - recipient: { - type: 'string', - description: 'Token recipient address (defaults to sender)', - }, - }, - handler: async ({ - context, - origin, - destination, - timeout, - quick, - relay, - warp, - wei, - recipient, - }) => { - await sendTestTransfer({ - context, - warpConfigPath: warp, - origin, - destination, - wei, - recipient, - timeoutSec: timeout, - skipWaitForDelivery: quick, - selfRelay: relay, - }); - process.exit(0); - }, -}; diff --git a/typescript/cli/src/commands/warp.ts b/typescript/cli/src/commands/warp.ts index c820ddd0d..7cb2893f5 100644 --- a/typescript/cli/src/commands/warp.ts +++ b/typescript/cli/src/commands/warp.ts @@ -3,15 +3,21 @@ import { CommandModule } from 'yargs'; import { EvmERC20WarpRouteReader } from '@hyperlane-xyz/sdk'; import { createWarpRouteDeployConfig } from '../config/warp.js'; -import { CommandModuleWithContext } from '../context/types.js'; +import { + CommandModuleWithContext, + CommandModuleWithWriteContext, +} from '../context/types.js'; import { log, logGray, logGreen } from '../logger.js'; +import { sendTestTransfer } from '../send/transfer.js'; import { writeFileAtPath } from '../utils/files.js'; import { addressCommandOption, chainCommandOption, outputFileCommandOption, + warpCoreConfigCommandOption, } from './options.js'; +import { MessageOptionsArgTypes, messageOptions } from './send.js'; /** * Parent command @@ -20,7 +26,12 @@ export const warpCommand: CommandModule = { command: 'warp', describe: 'Manage Hyperlane warp routes', builder: (yargs) => - yargs.command(config).command(read).version(false).demandCommand(), + yargs + .command(config) + .command(read) + .command(send) + .version(false) + .demandCommand(), handler: () => log('Command required'), }; @@ -88,3 +99,52 @@ export const read: CommandModuleWithContext<{ process.exit(0); }, }; + +const send: CommandModuleWithWriteContext< + MessageOptionsArgTypes & { + warp: string; + router?: string; + wei: string; + recipient?: string; + } +> = { + command: 'send', + describe: 'Send a test token transfer on a warp route', + builder: { + ...messageOptions, + warp: warpCoreConfigCommandOption, + wei: { + type: 'string', + description: 'Amount in wei to send', + default: 1, + }, + recipient: { + type: 'string', + description: 'Token recipient address (defaults to sender)', + }, + }, + handler: async ({ + context, + origin, + destination, + timeout, + quick, + relay, + warp, + wei, + recipient, + }) => { + await sendTestTransfer({ + context, + warpConfigPath: warp, + origin, + destination, + wei, + recipient, + timeoutSec: timeout, + skipWaitForDelivery: quick, + selfRelay: relay, + }); + process.exit(0); + }, +}; From 642bc686df0a48e7b30247d1921abfd572a53c56 Mon Sep 17 00:00:00 2001 From: Lee <6251863+ltyu@users.noreply.github.com> Date: Tue, 4 Jun 2024 15:08:12 -0400 Subject: [PATCH 20/73] refactor: rename chain and config commands (#3883) ### Description - Renames `chain` to `registry - `hl registry *` - Renames `config` to `configure` - `hl core configure` - `hl warp configure` ### Drive-by - Update `hl core configure` to prompt user for `owner` - Update key detection for cli `--key` - bug fix in core mailbox deploy ### Backward compatibility No ### Testing Manual --- .changeset/rich-humans-hug.md | 5 +++ typescript/cli/cli.ts | 4 +-- typescript/cli/src/commands/core.ts | 32 +++++++++++-------- .../src/commands/{chains.ts => registry.ts} | 4 +-- typescript/cli/src/commands/warp.ts | 7 ++-- typescript/cli/src/context/context.ts | 2 +- .../src/core/EvmCoreModule.hardhat-test.ts | 10 +++--- typescript/sdk/src/core/EvmCoreModule.ts | 2 +- 8 files changed, 39 insertions(+), 27 deletions(-) create mode 100644 .changeset/rich-humans-hug.md rename typescript/cli/src/commands/{chains.ts => registry.ts} (97%) diff --git a/.changeset/rich-humans-hug.md b/.changeset/rich-humans-hug.md new file mode 100644 index 000000000..367fece44 --- /dev/null +++ b/.changeset/rich-humans-hug.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/cli': minor +--- + +Rename chain and config commands. Update hl core configure to prompt user for owner. diff --git a/typescript/cli/cli.ts b/typescript/cli/cli.ts index 9ba398e02..17351e8ca 100644 --- a/typescript/cli/cli.ts +++ b/typescript/cli/cli.ts @@ -6,7 +6,6 @@ import type { LogFormat, LogLevel } from '@hyperlane-xyz/utils'; import './env.js'; import { avsCommand } from './src/commands/avs.js'; -import { chainsCommand } from './src/commands/chains.js'; import { configCommand } from './src/commands/config.js'; import { coreCommand } from './src/commands/core.js'; import { deployCommand } from './src/commands/deploy.js'; @@ -18,6 +17,7 @@ import { registryUriCommandOption, skipConfirmationOption, } from './src/commands/options.js'; +import { registryCommand } from './src/commands/registry.js'; import { sendCommand } from './src/commands/send.js'; import { statusCommand } from './src/commands/status.js'; import { validatorCommand } from './src/commands/validator.js'; @@ -51,10 +51,10 @@ try { contextMiddleware, ]) .command(avsCommand) - .command(chainsCommand) .command(configCommand) .command(coreCommand) .command(deployCommand) + .command(registryCommand) .command(sendCommand) .command(statusCommand) .command(validatorCommand) diff --git a/typescript/cli/src/commands/core.ts b/typescript/cli/src/commands/core.ts index 69f95fc1c..a8da2db49 100644 --- a/typescript/cli/src/commands/core.ts +++ b/typescript/cli/src/commands/core.ts @@ -1,11 +1,6 @@ import { CommandModule } from 'yargs'; -import { - EvmCoreReader, - HookConfigSchema, - IsmConfig, - IsmConfigSchema, -} from '@hyperlane-xyz/sdk'; +import { CoreConfigSchema, EvmCoreReader, IsmConfig } from '@hyperlane-xyz/sdk'; import { createHookConfig } from '../config/hooks.js'; import { createIsmConfig, createTrustedRelayerConfig } from '../config/ism.js'; @@ -17,6 +12,7 @@ import { logGray, logRed, } from '../logger.js'; +import { detectAndConfirmOrPrompt } from '../utils/chains.js'; import { writeYamlOrJson } from '../utils/files.js'; import { deploy } from './deploy.js'; @@ -30,19 +26,19 @@ export const coreCommand: CommandModule = { describe: 'Manage core Hyperlane contracts & configs', builder: (yargs) => yargs + .command(configure) .command(deploy) - .command(config) .command(read) .version(false) .demandCommand(), handler: () => log('Command required'), }; -export const config: CommandModuleWithContext<{ +export const configure: CommandModuleWithContext<{ ismAdvanced: boolean; config: string; }> = { - command: 'config', + command: 'configure', describe: 'Create a core configuration, including ISMs and hooks.', builder: { ismAdvanced: { @@ -60,6 +56,12 @@ export const config: CommandModuleWithContext<{ logGray('Hyperlane Core Configure'); logGray('------------------------'); + const owner = await detectAndConfirmOrPrompt( + async () => context.signer?.getAddress(), + 'Enter the desired', + 'owner address', + ); + // Create default Ism config (advanced or trusted) let defaultIsm: IsmConfig; if (ismAdvanced) { @@ -84,11 +86,15 @@ export const config: CommandModuleWithContext<{ ); // Validate - IsmConfigSchema.parse(defaultIsm); - HookConfigSchema.parse(requiredHook); - HookConfigSchema.parse(defaultHook); + const coreConfig = { + owner, + defaultIsm, + defaultHook, + requiredHook, + }; + CoreConfigSchema.parse(coreConfig); - writeYamlOrJson(configFilePath, { defaultIsm, defaultHook, requiredHook }); + writeYamlOrJson(configFilePath, coreConfig); process.exit(0); }, diff --git a/typescript/cli/src/commands/chains.ts b/typescript/cli/src/commands/registry.ts similarity index 97% rename from typescript/cli/src/commands/chains.ts rename to typescript/cli/src/commands/registry.ts index 9d0e970fa..e6847a3a3 100644 --- a/typescript/cli/src/commands/chains.ts +++ b/typescript/cli/src/commands/registry.ts @@ -9,8 +9,8 @@ type ChainType = (typeof ChainTypes)[number]; /** * Parent command */ -export const chainsCommand: CommandModule = { - command: 'chains', +export const registryCommand: CommandModule = { + command: 'registry', describe: 'View information about Hyperlane chains in a registry', builder: (yargs) => yargs diff --git a/typescript/cli/src/commands/warp.ts b/typescript/cli/src/commands/warp.ts index 7cb2893f5..5a430c619 100644 --- a/typescript/cli/src/commands/warp.ts +++ b/typescript/cli/src/commands/warp.ts @@ -27,19 +27,20 @@ export const warpCommand: CommandModule = { describe: 'Manage Hyperlane warp routes', builder: (yargs) => yargs - .command(config) + .command(configure) .command(read) .command(send) .version(false) .demandCommand(), + handler: () => log('Command required'), }; -export const config: CommandModuleWithContext<{ +export const configure: CommandModuleWithContext<{ ismAdvanced: boolean; out: string; }> = { - command: 'config', + command: 'configure', describe: 'Create a warp route configuration.', builder: { ismAdvanced: { diff --git a/typescript/cli/src/context/context.ts b/typescript/cli/src/context/context.ts index 23291a8ce..bc3258c3f 100644 --- a/typescript/cli/src/context/context.ts +++ b/typescript/cli/src/context/context.ts @@ -56,7 +56,7 @@ export async function getContext({ const registry = getRegistry(registryUri, registryOverrideUri); let signer: ethers.Wallet | undefined = undefined; - if (requiresKey) { + if (key || requiresKey) { ({ key, signer } = await getSigner({ key, skipConfirmation })); } const multiProvider = await getMultiProvider(registry, signer); diff --git a/typescript/sdk/src/core/EvmCoreModule.hardhat-test.ts b/typescript/sdk/src/core/EvmCoreModule.hardhat-test.ts index 9233c0455..7189718f1 100644 --- a/typescript/sdk/src/core/EvmCoreModule.hardhat-test.ts +++ b/typescript/sdk/src/core/EvmCoreModule.hardhat-test.ts @@ -17,10 +17,12 @@ import { MultiProvider } from '../providers/MultiProvider.js'; import { testCoreConfig } from '../test/testUtils.js'; import { EvmCoreModule } from './EvmCoreModule.js'; +import { CoreConfig } from './types.js'; describe('EvmCoreModule', async () => { const CHAIN = TestChainName.test1; const DELAY = 1892391283182; + let config: CoreConfig; let signer: SignerWithAddress; let multiProvider: MultiProvider; let evmCoreModule: EvmCoreModule; @@ -33,7 +35,7 @@ describe('EvmCoreModule', async () => { before(async () => { [signer] = await hre.ethers.getSigners(); multiProvider = MultiProvider.createTestMultiProvider({ signer }); - const config = { + config = { ...testCoreConfig([CHAIN])[CHAIN], upgrade: { timelock: { @@ -122,10 +124,8 @@ describe('EvmCoreModule', async () => { ); }); - it('should set mailbox owner to proxyAdmin', async () => { - expect(await mailboxContract.owner()).to.equal( - evmCoreModule.serialize().proxyAdmin, - ); + it('should set mailbox owner to config owner', async () => { + expect(await mailboxContract.owner()).to.equal(config.owner); }); it('should deploy mailbox default Ism', async () => { diff --git a/typescript/sdk/src/core/EvmCoreModule.ts b/typescript/sdk/src/core/EvmCoreModule.ts index 6887fbd1d..2fbfe4bd6 100644 --- a/typescript/sdk/src/core/EvmCoreModule.ts +++ b/typescript/sdk/src/core/EvmCoreModule.ts @@ -263,7 +263,7 @@ export class EvmCoreModule extends HyperlaneModule< await multiProvider.handleTx( chain, mailbox.initialize( - proxyAdmin, + config.owner, deployedDefaultIsm, deployedDefaultHook.address, deployedRequiredHook.address, From fa237e0f7a592e91d924f6120cf694ca5114b023 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Noah=20Bayindirli=20=F0=9F=A5=82?= Date: Tue, 4 Jun 2024 16:01:37 -0400 Subject: [PATCH 21/73] chore(cli,sdk,core,utils): release 4.0.0-alpha (#3892) #### will run `npm publish --tag alpha` once approved and merged from `cli-2.0` branch to `main` --- .changeset/beige-suns-wave.md | 5 -- .changeset/brave-humans-draw.md | 7 -- .changeset/bright-islands-shake.md | 6 -- .changeset/cuddly-trains-rescue.md | 5 -- .changeset/great-cycles-tickle.md | 5 -- .changeset/hip-squids-lay.md | 5 -- .changeset/orange-lemons-shake.md | 7 -- .changeset/rich-humans-hug.md | 5 -- .changeset/selfish-carpets-do.md | 5 -- .changeset/spotty-days-worry.md | 5 -- .changeset/spotty-wolves-fail.md | 5 -- .changeset/strong-rockets-kiss.md | 6 -- .changeset/three-coats-juggle.md | 5 -- .changeset/yellow-donkeys-fetch.md | 5 -- solidity/CHANGELOG.md | 12 +++ solidity/package.json | 4 +- typescript/ccip-server/CHANGELOG.md | 2 + typescript/ccip-server/package.json | 2 +- typescript/cli/CHANGELOG.md | 32 +++++++ typescript/cli/package.json | 6 +- typescript/cli/src/version.ts | 2 +- typescript/helloworld/CHANGELOG.md | 16 ++++ typescript/helloworld/package.json | 6 +- typescript/infra/CHANGELOG.md | 22 +++++ typescript/infra/package.json | 8 +- typescript/sdk/CHANGELOG.md | 24 ++++++ typescript/sdk/package.json | 6 +- typescript/utils/CHANGELOG.md | 10 +++ typescript/utils/package.json | 2 +- yarn.lock | 124 ++++++++++++++-------------- 30 files changed, 198 insertions(+), 156 deletions(-) delete mode 100644 .changeset/beige-suns-wave.md delete mode 100644 .changeset/brave-humans-draw.md delete mode 100644 .changeset/bright-islands-shake.md delete mode 100644 .changeset/cuddly-trains-rescue.md delete mode 100644 .changeset/great-cycles-tickle.md delete mode 100644 .changeset/hip-squids-lay.md delete mode 100644 .changeset/orange-lemons-shake.md delete mode 100644 .changeset/rich-humans-hug.md delete mode 100644 .changeset/selfish-carpets-do.md delete mode 100644 .changeset/spotty-days-worry.md delete mode 100644 .changeset/spotty-wolves-fail.md delete mode 100644 .changeset/strong-rockets-kiss.md delete mode 100644 .changeset/three-coats-juggle.md delete mode 100644 .changeset/yellow-donkeys-fetch.md diff --git a/.changeset/beige-suns-wave.md b/.changeset/beige-suns-wave.md deleted file mode 100644 index 277f99d47..000000000 --- a/.changeset/beige-suns-wave.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperlane-xyz/sdk': minor ---- - -Add create() with EvmCoreModule diff --git a/.changeset/brave-humans-draw.md b/.changeset/brave-humans-draw.md deleted file mode 100644 index 4548de7de..000000000 --- a/.changeset/brave-humans-draw.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@hyperlane-xyz/infra': minor -'@hyperlane-xyz/cli': minor -'@hyperlane-xyz/sdk': minor ---- - -Support hook config objects in warp config diff --git a/.changeset/bright-islands-shake.md b/.changeset/bright-islands-shake.md deleted file mode 100644 index 80f10d2d7..000000000 --- a/.changeset/bright-islands-shake.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@hyperlane-xyz/cli': minor -'@hyperlane-xyz/sdk': minor ---- - -Implement hyperlane core config to return CoreConfig diff --git a/.changeset/cuddly-trains-rescue.md b/.changeset/cuddly-trains-rescue.md deleted file mode 100644 index 11ed54df7..000000000 --- a/.changeset/cuddly-trains-rescue.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperlane-xyz/sdk': minor ---- - -Add EvmIcaModule.create() diff --git a/.changeset/great-cycles-tickle.md b/.changeset/great-cycles-tickle.md deleted file mode 100644 index 2c7661bc7..000000000 --- a/.changeset/great-cycles-tickle.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperlane-xyz/cli': minor ---- - -Adds 'hyperlane warp config'. diff --git a/.changeset/hip-squids-lay.md b/.changeset/hip-squids-lay.md deleted file mode 100644 index 4162f7e76..000000000 --- a/.changeset/hip-squids-lay.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperlane-xyz/cli': minor ---- - -Adds 'hyperlane core config'. diff --git a/.changeset/orange-lemons-shake.md b/.changeset/orange-lemons-shake.md deleted file mode 100644 index 8e97e51a1..000000000 --- a/.changeset/orange-lemons-shake.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@hyperlane-xyz/infra': minor -'@hyperlane-xyz/utils': minor -'@hyperlane-xyz/sdk': minor ---- - -Completes the EvmIsmModule for creating, reading and updating ISMs. diff --git a/.changeset/rich-humans-hug.md b/.changeset/rich-humans-hug.md deleted file mode 100644 index 367fece44..000000000 --- a/.changeset/rich-humans-hug.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperlane-xyz/cli': minor ---- - -Rename chain and config commands. Update hl core configure to prompt user for owner. diff --git a/.changeset/selfish-carpets-do.md b/.changeset/selfish-carpets-do.md deleted file mode 100644 index a410aec2a..000000000 --- a/.changeset/selfish-carpets-do.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperlane-xyz/sdk': minor ---- - -Adds zod validation on tx submitter populated txs & re-uses ICA params for transformer. diff --git a/.changeset/spotty-days-worry.md b/.changeset/spotty-days-worry.md deleted file mode 100644 index cee84d1d3..000000000 --- a/.changeset/spotty-days-worry.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperlane-xyz/cli': minor ---- - -Add warp send in favor of send transfer. diff --git a/.changeset/spotty-wolves-fail.md b/.changeset/spotty-wolves-fail.md deleted file mode 100644 index 73f74a044..000000000 --- a/.changeset/spotty-wolves-fail.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperlane-xyz/cli': minor ---- - -Adds 'hyperlane warp read'. diff --git a/.changeset/strong-rockets-kiss.md b/.changeset/strong-rockets-kiss.md deleted file mode 100644 index 48a1d1948..000000000 --- a/.changeset/strong-rockets-kiss.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@hyperlane-xyz/cli': minor -'@hyperlane-xyz/sdk': minor ---- - -Implements `hyperlane core deploy` diff --git a/.changeset/three-coats-juggle.md b/.changeset/three-coats-juggle.md deleted file mode 100644 index 1d70f125f..000000000 --- a/.changeset/three-coats-juggle.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperlane-xyz/cli': minor ---- - -Adds 'hyperlane core read'. diff --git a/.changeset/yellow-donkeys-fetch.md b/.changeset/yellow-donkeys-fetch.md deleted file mode 100644 index 66bc128e8..000000000 --- a/.changeset/yellow-donkeys-fetch.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperlane-xyz/sdk': minor ---- - -Adds further zod schema validation support throughout the SDK, namely for /transactions. diff --git a/solidity/CHANGELOG.md b/solidity/CHANGELOG.md index f8cee2164..b910b1140 100644 --- a/solidity/CHANGELOG.md +++ b/solidity/CHANGELOG.md @@ -1,5 +1,17 @@ # @hyperlane-xyz/core +## 4.0.0 + +### Major Changes + +- 74c879fa1: Merge branch 'cli-2.0' into main. + +### Patch Changes + +- Updated dependencies [341b8affd] +- Updated dependencies [74c879fa1] + - @hyperlane-xyz/utils@4.0.0-alpha + ## 3.13.0 ### Minor Changes diff --git a/solidity/package.json b/solidity/package.json index bdbe1f659..9d2789f0d 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.13.0", + "version": "4.0.0-alpha", "dependencies": { "@eth-optimism/contracts": "^0.6.0", - "@hyperlane-xyz/utils": "3.13.0", + "@hyperlane-xyz/utils": "4.0.0-alpha", "@layerzerolabs/lz-evm-oapp-v2": "2.0.2", "@openzeppelin/contracts": "^4.9.3", "@openzeppelin/contracts-upgradeable": "^v4.9.3", diff --git a/typescript/ccip-server/CHANGELOG.md b/typescript/ccip-server/CHANGELOG.md index 40e0e5390..dff035e58 100644 --- a/typescript/ccip-server/CHANGELOG.md +++ b/typescript/ccip-server/CHANGELOG.md @@ -1,5 +1,7 @@ # @hyperlane-xyz/ccip-server +## 4.0.0-alpha + ## 3.13.0 ## 3.12.0 diff --git a/typescript/ccip-server/package.json b/typescript/ccip-server/package.json index cc07a17b3..f6df925d7 100644 --- a/typescript/ccip-server/package.json +++ b/typescript/ccip-server/package.json @@ -1,6 +1,6 @@ { "name": "@hyperlane-xyz/ccip-server", - "version": "3.13.0", + "version": "4.0.0-alpha", "description": "CCIP server", "typings": "dist/index.d.ts", "typedocMain": "src/index.ts", diff --git a/typescript/cli/CHANGELOG.md b/typescript/cli/CHANGELOG.md index c62b58046..ee64a3328 100644 --- a/typescript/cli/CHANGELOG.md +++ b/typescript/cli/CHANGELOG.md @@ -1,5 +1,37 @@ # @hyperlane-xyz/cli +## 4.0.0-alpha + +### Major Changes + +- 74c879fa1: Merge branch 'cli-2.0' into main. + +### Minor Changes + +- 1ec61debd: Support hook config objects in warp config +- 4663018fc: Implement hyperlane core config to return CoreConfig +- 84bc0bd7f: Adds 'hyperlane warp config'. +- b560bfc26: Adds 'hyperlane core config'. +- 642bc686d: Rename chain and config commands. Update hl core configure to prompt user for owner. +- ba4c9a7da: Add warp send in favor of send transfer. +- 7089c910f: Adds 'hyperlane warp read'. +- 1d0d1bb36: Implements `hyperlane core deploy` +- 44a2ffa1b: Adds 'hyperlane core read'. + +### Patch Changes + +- Updated dependencies [eb23e7729] +- Updated dependencies [1ec61debd] +- Updated dependencies [4663018fc] +- Updated dependencies [5e5886f2c] +- Updated dependencies [341b8affd] +- Updated dependencies [3dabcbdca] +- Updated dependencies [1d0d1bb36] +- Updated dependencies [74c879fa1] +- Updated dependencies [4bf7301ea] + - @hyperlane-xyz/sdk@4.0.0 + - @hyperlane-xyz/utils@4.0.0-alpha + ## 3.13.0 ### Minor Changes diff --git a/typescript/cli/package.json b/typescript/cli/package.json index 645d3a48b..33da93e66 100644 --- a/typescript/cli/package.json +++ b/typescript/cli/package.json @@ -1,13 +1,13 @@ { "name": "@hyperlane-xyz/cli", - "version": "3.13.0", + "version": "4.0.0-alpha", "description": "A command-line utility for common Hyperlane operations", "dependencies": { "@aws-sdk/client-kms": "^3.577.0", "@aws-sdk/client-s3": "^3.577.0", "@hyperlane-xyz/registry": "1.3.0", - "@hyperlane-xyz/sdk": "3.13.0", - "@hyperlane-xyz/utils": "3.13.0", + "@hyperlane-xyz/sdk": "4.0.0-alpha", + "@hyperlane-xyz/utils": "4.0.0-alpha", "@inquirer/prompts": "^3.0.0", "asn1.js": "^5.4.1", "bignumber.js": "^9.1.1", diff --git a/typescript/cli/src/version.ts b/typescript/cli/src/version.ts index 9fb80ae08..c94009698 100644 --- a/typescript/cli/src/version.ts +++ b/typescript/cli/src/version.ts @@ -1 +1 @@ -export const VERSION = '3.13.0'; +export const VERSION = '4.0.0-alpha'; diff --git a/typescript/helloworld/CHANGELOG.md b/typescript/helloworld/CHANGELOG.md index f5e8404be..db79a5bfb 100644 --- a/typescript/helloworld/CHANGELOG.md +++ b/typescript/helloworld/CHANGELOG.md @@ -1,5 +1,21 @@ # @hyperlane-xyz/helloworld +## 4.0.0-alpha + +### Patch Changes + +- Updated dependencies [eb23e7729] +- Updated dependencies [1ec61debd] +- Updated dependencies [4663018fc] +- Updated dependencies [5e5886f2c] +- Updated dependencies [341b8affd] +- Updated dependencies [3dabcbdca] +- Updated dependencies [1d0d1bb36] +- Updated dependencies [74c879fa1] +- Updated dependencies [4bf7301ea] + - @hyperlane-xyz/sdk@4.0.0-alpha + - @hyperlane-xyz/core@4.0.0-alpha + ## 3.13.0 ### Patch Changes diff --git a/typescript/helloworld/package.json b/typescript/helloworld/package.json index 7d3f62b3d..8e78ca871 100644 --- a/typescript/helloworld/package.json +++ b/typescript/helloworld/package.json @@ -1,11 +1,11 @@ { "name": "@hyperlane-xyz/helloworld", "description": "A basic skeleton of an Hyperlane app", - "version": "3.13.0", + "version": "4.0.0-alpha", "dependencies": { - "@hyperlane-xyz/core": "3.13.0", + "@hyperlane-xyz/core": "4.0.0-alpha", "@hyperlane-xyz/registry": "1.3.0", - "@hyperlane-xyz/sdk": "3.13.0", + "@hyperlane-xyz/sdk": "4.0.0-alpha", "@openzeppelin/contracts-upgradeable": "^4.9.3", "ethers": "^5.7.2" }, diff --git a/typescript/infra/CHANGELOG.md b/typescript/infra/CHANGELOG.md index 0696269d8..a960a7da3 100644 --- a/typescript/infra/CHANGELOG.md +++ b/typescript/infra/CHANGELOG.md @@ -1,5 +1,27 @@ # @hyperlane-xyz/infra +## 4.0.0-alpha + +### Minor Changes + +- 1ec61debd: Support hook config objects in warp config +- 341b8affd: Completes the EvmIsmModule for creating, reading and updating ISMs. + +### Patch Changes + +- Updated dependencies [eb23e7729] +- Updated dependencies [1ec61debd] +- Updated dependencies [4663018fc] +- Updated dependencies [5e5886f2c] +- Updated dependencies [341b8affd] +- Updated dependencies [3dabcbdca] +- Updated dependencies [1d0d1bb36] +- Updated dependencies [74c879fa1] +- Updated dependencies [4bf7301ea] + - @hyperlane-xyz/sdk@4.0.0-alpha + - @hyperlane-xyz/utils@4.0.0-alpha + - @hyperlane-xyz/helloworld@4.0.0-alpha + ## 3.13.0 ### Minor Changes diff --git a/typescript/infra/package.json b/typescript/infra/package.json index 33f8d6194..dbced2f38 100644 --- a/typescript/infra/package.json +++ b/typescript/infra/package.json @@ -1,7 +1,7 @@ { "name": "@hyperlane-xyz/infra", "description": "Infrastructure utilities for the Hyperlane Network", - "version": "3.13.0", + "version": "4.0.0-alpha", "dependencies": { "@arbitrum/sdk": "^3.0.0", "@aws-sdk/client-iam": "^3.74.0", @@ -12,10 +12,10 @@ "@ethersproject/experimental": "^5.7.0", "@ethersproject/hardware-wallets": "^5.7.0", "@ethersproject/providers": "^5.7.2", - "@hyperlane-xyz/helloworld": "3.13.0", + "@hyperlane-xyz/helloworld": "4.0.0-alpha", "@hyperlane-xyz/registry": "1.3.0", - "@hyperlane-xyz/sdk": "3.13.0", - "@hyperlane-xyz/utils": "3.13.0", + "@hyperlane-xyz/sdk": "4.0.0-alpha", + "@hyperlane-xyz/utils": "4.0.0-alpha", "@nomiclabs/hardhat-etherscan": "^3.0.3", "@solana/web3.js": "^1.78.0", "asn1.js": "5.4.1", diff --git a/typescript/sdk/CHANGELOG.md b/typescript/sdk/CHANGELOG.md index b484d11bc..293aff181 100644 --- a/typescript/sdk/CHANGELOG.md +++ b/typescript/sdk/CHANGELOG.md @@ -1,5 +1,29 @@ # @hyperlane-xyz/sdk +## 4.0.0-alpha + +### Major Changes + +- 74c879fa1: Merge branch 'cli-2.0' into main. + +### Minor Changes + +- eb23e7729: Add create() with EvmCoreModule +- 1ec61debd: Support hook config objects in warp config +- 4663018fc: Implement hyperlane core config to return CoreConfig +- 5e5886f2c: Add EvmIcaModule.create() +- 341b8affd: Completes the EvmIsmModule for creating, reading and updating ISMs. +- 3dabcbdca: Adds zod validation on tx submitter populated txs & re-uses ICA params for transformer. +- 1d0d1bb36: Implements `hyperlane core deploy` +- 4bf7301ea: Adds further zod schema validation support throughout the SDK, namely for /transactions. + +### Patch Changes + +- Updated dependencies [341b8affd] +- Updated dependencies [74c879fa1] + - @hyperlane-xyz/utils@4.0.0-alpha + - @hyperlane-xyz/core@4.0.0-alpha + ## 3.13.0 ### Minor Changes diff --git a/typescript/sdk/package.json b/typescript/sdk/package.json index ef58f43cd..094d8764a 100644 --- a/typescript/sdk/package.json +++ b/typescript/sdk/package.json @@ -1,13 +1,13 @@ { "name": "@hyperlane-xyz/sdk", "description": "The official SDK for the Hyperlane Network", - "version": "3.13.0", + "version": "4.0.0-alpha", "dependencies": { "@aws-sdk/client-s3": "^3.74.0", "@cosmjs/cosmwasm-stargate": "^0.31.3", "@cosmjs/stargate": "^0.31.3", - "@hyperlane-xyz/core": "3.13.0", - "@hyperlane-xyz/utils": "3.13.0", + "@hyperlane-xyz/core": "4.0.0-alpha", + "@hyperlane-xyz/utils": "4.0.0-alpha", "@safe-global/api-kit": "1.3.0", "@safe-global/protocol-kit": "1.3.0", "@solana/spl-token": "^0.3.8", diff --git a/typescript/utils/CHANGELOG.md b/typescript/utils/CHANGELOG.md index 0dce2d1b6..4a7c2aaac 100644 --- a/typescript/utils/CHANGELOG.md +++ b/typescript/utils/CHANGELOG.md @@ -1,5 +1,15 @@ # @hyperlane-xyz/utils +## 4.0.0-alpha + +### Major Changes + +- 74c879fa1: Merge branch 'cli-2.0' into main. + +### Minor Changes + +- 341b8affd: Completes the EvmIsmModule for creating, reading and updating ISMs. + ## 3.13.0 ### Minor Changes diff --git a/typescript/utils/package.json b/typescript/utils/package.json index 260348910..5147cbd26 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.13.0", + "version": "4.0.0-alpha", "dependencies": { "@cosmjs/encoding": "^0.31.3", "@solana/web3.js": "^1.78.0", diff --git a/yarn.lock b/yarn.lock index 9fc2aab50..f2cda87c1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5691,8 +5691,8 @@ __metadata: "@aws-sdk/client-kms": "npm:^3.577.0" "@aws-sdk/client-s3": "npm:^3.577.0" "@hyperlane-xyz/registry": "npm:1.3.0" - "@hyperlane-xyz/sdk": "npm:3.13.0" - "@hyperlane-xyz/utils": "npm:3.13.0" + "@hyperlane-xyz/sdk": "npm:4.0.0-alpha" + "@hyperlane-xyz/utils": "npm:4.0.0-alpha" "@inquirer/prompts": "npm:^3.0.0" "@types/mocha": "npm:^10.0.1" "@types/node": "npm:^18.14.5" @@ -5720,12 +5720,28 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/core@npm:3.13.0, @hyperlane-xyz/core@workspace:solidity": +"@hyperlane-xyz/core@npm:3.7.0": + version: 3.7.0 + resolution: "@hyperlane-xyz/core@npm:3.7.0" + dependencies: + "@eth-optimism/contracts": "npm:^0.6.0" + "@hyperlane-xyz/utils": "npm:3.7.0" + "@openzeppelin/contracts": "npm:^4.9.3" + "@openzeppelin/contracts-upgradeable": "npm:^v4.9.3" + peerDependencies: + "@ethersproject/abi": "*" + "@ethersproject/providers": "*" + "@types/sinon-chai": "*" + checksum: efa01d943dd5b67830bb7244291c8ba9849472e804dff589463de76d3c03e56bc8d62454b575a6621aa1b8b53cc0d1d3b752a83d34f4b328ecd85e1ff23230d5 + languageName: node + linkType: hard + +"@hyperlane-xyz/core@npm:4.0.0-alpha, @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.13.0" + "@hyperlane-xyz/utils": "npm:4.0.0-alpha" "@layerzerolabs/lz-evm-oapp-v2": "npm:2.0.2" "@layerzerolabs/solidity-examples": "npm:^1.1.0" "@nomiclabs/hardhat-ethers": "npm:^2.2.3" @@ -5757,29 +5773,13 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/core@npm:3.7.0": - version: 3.7.0 - resolution: "@hyperlane-xyz/core@npm:3.7.0" - dependencies: - "@eth-optimism/contracts": "npm:^0.6.0" - "@hyperlane-xyz/utils": "npm:3.7.0" - "@openzeppelin/contracts": "npm:^4.9.3" - "@openzeppelin/contracts-upgradeable": "npm:^v4.9.3" - peerDependencies: - "@ethersproject/abi": "*" - "@ethersproject/providers": "*" - "@types/sinon-chai": "*" - checksum: efa01d943dd5b67830bb7244291c8ba9849472e804dff589463de76d3c03e56bc8d62454b575a6621aa1b8b53cc0d1d3b752a83d34f4b328ecd85e1ff23230d5 - languageName: node - linkType: hard - -"@hyperlane-xyz/helloworld@npm:3.13.0, @hyperlane-xyz/helloworld@workspace:typescript/helloworld": +"@hyperlane-xyz/helloworld@npm:4.0.0-alpha, @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.13.0" + "@hyperlane-xyz/core": "npm:4.0.0-alpha" "@hyperlane-xyz/registry": "npm:1.3.0" - "@hyperlane-xyz/sdk": "npm:3.13.0" + "@hyperlane-xyz/sdk": "npm:4.0.0-alpha" "@nomiclabs/hardhat-ethers": "npm:^2.2.3" "@nomiclabs/hardhat-waffle": "npm:^2.0.6" "@openzeppelin/contracts-upgradeable": "npm:^4.9.3" @@ -5824,10 +5824,10 @@ __metadata: "@ethersproject/experimental": "npm:^5.7.0" "@ethersproject/hardware-wallets": "npm:^5.7.0" "@ethersproject/providers": "npm:^5.7.2" - "@hyperlane-xyz/helloworld": "npm:3.13.0" + "@hyperlane-xyz/helloworld": "npm:4.0.0-alpha" "@hyperlane-xyz/registry": "npm:1.3.0" - "@hyperlane-xyz/sdk": "npm:3.13.0" - "@hyperlane-xyz/utils": "npm:3.13.0" + "@hyperlane-xyz/sdk": "npm:4.0.0-alpha" + "@hyperlane-xyz/utils": "npm:4.0.0-alpha" "@nomiclabs/hardhat-ethers": "npm:^2.2.3" "@nomiclabs/hardhat-etherscan": "npm:^3.0.3" "@nomiclabs/hardhat-waffle": "npm:^2.0.6" @@ -5887,15 +5887,43 @@ __metadata: languageName: node linkType: hard -"@hyperlane-xyz/sdk@npm:3.13.0, @hyperlane-xyz/sdk@workspace:typescript/sdk": +"@hyperlane-xyz/sdk@npm:3.7.0": + version: 3.7.0 + resolution: "@hyperlane-xyz/sdk@npm:3.7.0" + dependencies: + "@cosmjs/cosmwasm-stargate": "npm:^0.31.3" + "@cosmjs/stargate": "npm:^0.31.3" + "@hyperlane-xyz/core": "npm:3.7.0" + "@hyperlane-xyz/utils": "npm:3.7.0" + "@solana/spl-token": "npm:^0.3.8" + "@solana/web3.js": "npm:^1.78.0" + "@types/coingecko-api": "npm:^1.0.10" + "@types/debug": "npm:^4.1.7" + "@wagmi/chains": "npm:^1.8.0" + bignumber.js: "npm:^9.1.1" + coingecko-api: "npm:^1.0.10" + cosmjs-types: "npm:^0.9.0" + cross-fetch: "npm:^3.1.5" + debug: "npm:^4.3.4" + ethers: "npm:^5.7.2" + viem: "npm:^1.20.0" + zod: "npm:^3.21.2" + peerDependencies: + "@ethersproject/abi": "*" + "@ethersproject/providers": "*" + checksum: b124a42f34502c4dad4127723d345158f592056d7e60e17d87c84bf81664ead20232ffaff66e6c21968dfd5693ba5122910fbcaa6b7db5b05fdd5d2051592835 + languageName: node + linkType: hard + +"@hyperlane-xyz/sdk@npm:4.0.0-alpha, @hyperlane-xyz/sdk@workspace:typescript/sdk": version: 0.0.0-use.local resolution: "@hyperlane-xyz/sdk@workspace:typescript/sdk" dependencies: "@aws-sdk/client-s3": "npm:^3.74.0" "@cosmjs/cosmwasm-stargate": "npm:^0.31.3" "@cosmjs/stargate": "npm:^0.31.3" - "@hyperlane-xyz/core": "npm:3.13.0" - "@hyperlane-xyz/utils": "npm:3.13.0" + "@hyperlane-xyz/core": "npm:4.0.0-alpha" + "@hyperlane-xyz/utils": "npm:4.0.0-alpha" "@nomiclabs/hardhat-ethers": "npm:^2.2.3" "@nomiclabs/hardhat-waffle": "npm:^2.0.6" "@safe-global/api-kit": "npm:1.3.0" @@ -5935,35 +5963,19 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/sdk@npm:3.7.0": +"@hyperlane-xyz/utils@npm:3.7.0": version: 3.7.0 - resolution: "@hyperlane-xyz/sdk@npm:3.7.0" + resolution: "@hyperlane-xyz/utils@npm:3.7.0" dependencies: - "@cosmjs/cosmwasm-stargate": "npm:^0.31.3" - "@cosmjs/stargate": "npm:^0.31.3" - "@hyperlane-xyz/core": "npm:3.7.0" - "@hyperlane-xyz/utils": "npm:3.7.0" - "@solana/spl-token": "npm:^0.3.8" + "@cosmjs/encoding": "npm:^0.31.3" "@solana/web3.js": "npm:^1.78.0" - "@types/coingecko-api": "npm:^1.0.10" - "@types/debug": "npm:^4.1.7" - "@wagmi/chains": "npm:^1.8.0" bignumber.js: "npm:^9.1.1" - coingecko-api: "npm:^1.0.10" - cosmjs-types: "npm:^0.9.0" - cross-fetch: "npm:^3.1.5" - debug: "npm:^4.3.4" ethers: "npm:^5.7.2" - viem: "npm:^1.20.0" - zod: "npm:^3.21.2" - peerDependencies: - "@ethersproject/abi": "*" - "@ethersproject/providers": "*" - checksum: b124a42f34502c4dad4127723d345158f592056d7e60e17d87c84bf81664ead20232ffaff66e6c21968dfd5693ba5122910fbcaa6b7db5b05fdd5d2051592835 + checksum: c76f36913c572702b9dfe22fd868db6fed01c0da9485319e33e8d00a6b8a1bfdcecb5f61c8a3fd8ccbef0b36809e8055db62d75d0c6759d5e079ee330586bcd1 languageName: node linkType: hard -"@hyperlane-xyz/utils@npm:3.13.0, @hyperlane-xyz/utils@workspace:typescript/utils": +"@hyperlane-xyz/utils@npm:4.0.0-alpha, @hyperlane-xyz/utils@workspace:typescript/utils": version: 0.0.0-use.local resolution: "@hyperlane-xyz/utils@workspace:typescript/utils" dependencies: @@ -5981,18 +5993,6 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/utils@npm:3.7.0": - version: 3.7.0 - resolution: "@hyperlane-xyz/utils@npm:3.7.0" - dependencies: - "@cosmjs/encoding": "npm:^0.31.3" - "@solana/web3.js": "npm:^1.78.0" - bignumber.js: "npm:^9.1.1" - ethers: "npm:^5.7.2" - checksum: c76f36913c572702b9dfe22fd868db6fed01c0da9485319e33e8d00a6b8a1bfdcecb5f61c8a3fd8ccbef0b36809e8055db62d75d0c6759d5e079ee330586bcd1 - languageName: node - linkType: hard - "@hyperlane-xyz/widgets@npm:3.7.0": version: 3.7.0 resolution: "@hyperlane-xyz/widgets@npm:3.7.0" From cc65ee8fa19cbf0ac5670c60f9f3af7c8fcad0dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Noah=20Bayindirli=20=F0=9F=A5=82?= Date: Wed, 5 Jun 2024 11:59:16 -0400 Subject: [PATCH 22/73] fix(cli): remove domainRoutingHook & interchainGasPaymaster(Hook) from hyperlane * configure list (#3897) ### Description * removes domainRoutingHook & interchainGasPaymaster(Hook) from hyperlane * configure list ### Drive-by changes - none ### Related issues - fixes https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/3896 ### Backward compatibility - yes ### Testing - ci-test --- typescript/cli/src/config/hooks.ts | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/typescript/cli/src/config/hooks.ts b/typescript/cli/src/config/hooks.ts index ed7baf267..48c913a46 100644 --- a/typescript/cli/src/config/hooks.ts +++ b/typescript/cli/src/config/hooks.ts @@ -80,24 +80,12 @@ export async function createHookConfig( 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, }); @@ -105,12 +93,8 @@ export async function createHookConfig( lastConfig = { type: HookType.MERKLE_TREE }; } else if (hookType === HookType.PROTOCOL_FEE) { lastConfig = await createProtocolFeeConfig(); - // } else if (hookType === HookType.INTERCHAIN_GAS_PAYMASTER) { - // lastConfig = await createIGPConfig(remotes); } else if (hookType === HookType.AGGREGATION) { lastConfig = await createAggregationConfig(context); - } else if (hookType === HookType.ROUTING) { - lastConfig = await createRoutingConfig(context); } else { throw new Error(`Invalid hook type: ${hookType}`); } From bdcbe1d168a6890f678fc9ebba964a5b4aebd557 Mon Sep 17 00:00:00 2001 From: Lee <6251863+ltyu@users.noreply.github.com> Date: Wed, 5 Jun 2024 20:40:58 -0400 Subject: [PATCH 23/73] feat: WarpModule with create() (#3838) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Description - Adds WarpModule with create logic. - Updates logic to WarpModuleReader ### Drive by - Adds CustomIsmConfig mentioned in https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/3773 ### Related issues - Fixes #3839 ### Backward compatibility Yes ### Testing Unit Tests --------- Signed-off-by: Paul Balaji Co-authored-by: Paul Balaji Co-authored-by: Yorke Rhodes Co-authored-by: J M Rossy Co-authored-by: Noah Bayindirli 🥂 Co-authored-by: Noah Bayindirli 🥂 --- .changeset/five-baboons-smoke.md | 5 + typescript/cli/src/config/ism.ts | 2 +- typescript/sdk/src/hook/types.ts | 1 + .../ism/HyperlaneIsmFactory.hardhat-test.ts | 4 +- typescript/sdk/src/ism/types.ts | 4 + .../token/EvmERC20WarpModule.hardhat-test.ts | 202 +++++++++++++++ .../sdk/src/token/EvmERC20WarpModule.ts | 93 +++++++ .../EvmERC20WarpRouteReader.hardhat-test.ts | 237 ++++++++++++++++++ .../sdk/src/token/EvmERC20WarpRouteReader.ts | 165 ++++++++---- typescript/sdk/src/token/config.ts | 5 + .../sdk/src/token/deploy.hardhat-test.ts | 9 +- 11 files changed, 675 insertions(+), 52 deletions(-) create mode 100644 .changeset/five-baboons-smoke.md create mode 100644 typescript/sdk/src/token/EvmERC20WarpModule.hardhat-test.ts create mode 100644 typescript/sdk/src/token/EvmERC20WarpModule.ts create mode 100644 typescript/sdk/src/token/EvmERC20WarpRouteReader.hardhat-test.ts diff --git a/.changeset/five-baboons-smoke.md b/.changeset/five-baboons-smoke.md new file mode 100644 index 000000000..3cebc87ac --- /dev/null +++ b/.changeset/five-baboons-smoke.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/sdk': minor +--- + +Add EvmWarpModule with create() diff --git a/typescript/cli/src/config/ism.ts b/typescript/cli/src/config/ism.ts index d16926aba..d3d90971f 100644 --- a/typescript/cli/src/config/ism.ts +++ b/typescript/cli/src/config/ism.ts @@ -58,7 +58,7 @@ export function readIsmConfig(filePath: string) { return parsedConfig; } -const ISM_TYPE_DESCRIPTIONS: Record = { +const ISM_TYPE_DESCRIPTIONS: Record = { [IsmType.MESSAGE_ID_MULTISIG]: 'Validators need to sign just this messageId', [IsmType.MERKLE_ROOT_MULTISIG]: 'Validators need to sign the root of the merkle tree of all messages from origin chain', diff --git a/typescript/sdk/src/hook/types.ts b/typescript/sdk/src/hook/types.ts index 8cbd26fd2..c0edaafce 100644 --- a/typescript/sdk/src/hook/types.ts +++ b/typescript/sdk/src/hook/types.ts @@ -27,6 +27,7 @@ export enum OnchainHookType { } export enum HookType { + CUSTOM = 'custom', MERKLE_TREE = 'merkleTreeHook', INTERCHAIN_GAS_PAYMASTER = 'interchainGasPaymaster', AGGREGATION = 'aggregationHook', diff --git a/typescript/sdk/src/ism/HyperlaneIsmFactory.hardhat-test.ts b/typescript/sdk/src/ism/HyperlaneIsmFactory.hardhat-test.ts index 180f49371..87cf1044a 100644 --- a/typescript/sdk/src/ism/HyperlaneIsmFactory.hardhat-test.ts +++ b/typescript/sdk/src/ism/HyperlaneIsmFactory.hardhat-test.ts @@ -353,13 +353,13 @@ describe('HyperlaneIsmFactory', async () => { }); 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([ TestChainName.test1, - 'test2', + TestChainName.test2, + TestChainName.test4, ]).result; ismFactoryDeployer = new HyperlaneProxyFactoryDeployer(multiProvider); ismFactory = new HyperlaneIsmFactory( diff --git a/typescript/sdk/src/ism/types.ts b/typescript/sdk/src/ism/types.ts index b1dc1fe64..631a7a5f5 100644 --- a/typescript/sdk/src/ism/types.ts +++ b/typescript/sdk/src/ism/types.ts @@ -2,6 +2,7 @@ import { z } from 'zod'; import { IAggregationIsm, + IInterchainSecurityModule, IMultisigIsm, IRoutingIsm, OPStackIsm, @@ -39,6 +40,7 @@ export enum ModuleType { // this enum can be adjusted as per deployments necessary // meant for the deployer and checker export enum IsmType { + CUSTOM = 'custom', OP_STACK = 'opStackIsm', ROUTING = 'domainRoutingIsm', FALLBACK_ROUTING = 'defaultFallbackRoutingIsm', @@ -73,6 +75,7 @@ export function ismTypeToModuleType(ismType: IsmType): ModuleType { case IsmType.OP_STACK: case IsmType.TEST_ISM: case IsmType.PAUSABLE: + case IsmType.CUSTOM: case IsmType.TRUSTED_RELAYER: return ModuleType.NULL; } @@ -111,6 +114,7 @@ export type AggregationIsmConfig = { export type IsmConfig = z.infer; export type DeployedIsmType = { + [IsmType.CUSTOM]: IInterchainSecurityModule; [IsmType.ROUTING]: IRoutingIsm; [IsmType.FALLBACK_ROUTING]: IRoutingIsm; [IsmType.AGGREGATION]: IAggregationIsm; diff --git a/typescript/sdk/src/token/EvmERC20WarpModule.hardhat-test.ts b/typescript/sdk/src/token/EvmERC20WarpModule.hardhat-test.ts new file mode 100644 index 000000000..bf64cfa17 --- /dev/null +++ b/typescript/sdk/src/token/EvmERC20WarpModule.hardhat-test.ts @@ -0,0 +1,202 @@ +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers.js'; +import { expect } from 'chai'; +import { constants } from 'ethers'; +import hre from 'hardhat'; + +import { + ERC20Test, + ERC20Test__factory, + ERC4626Test__factory, + GasRouter, + HypERC20CollateralVaultDeposit__factory, + HypERC20__factory, + HypNative__factory, + Mailbox, + Mailbox__factory, +} from '@hyperlane-xyz/core'; +import { + HyperlaneContractsMap, + RouterConfig, + TestChainName, +} from '@hyperlane-xyz/sdk'; + +import { TestCoreApp } from '../core/TestCoreApp.js'; +import { TestCoreDeployer } from '../core/TestCoreDeployer.js'; +import { HyperlaneProxyFactoryDeployer } from '../deploy/HyperlaneProxyFactoryDeployer.js'; +import { ProxyFactoryFactories } from '../deploy/contracts.js'; +import { HyperlaneIsmFactory } from '../ism/HyperlaneIsmFactory.js'; +import { MultiProvider } from '../providers/MultiProvider.js'; +import { ChainMap } from '../types.js'; + +import { EvmERC20WarpModule } from './EvmERC20WarpModule.js'; +import { TokenType } from './config.js'; +import { TokenRouterConfig } from './schemas.js'; + +describe('EvmERC20WarpHyperlaneModule', async () => { + const TOKEN_NAME = 'fake'; + const TOKEN_SUPPLY = '100000000000000000000'; + const TOKEN_DECIMALS = 18; + const chain = TestChainName.test4; + let mailbox: Mailbox; + let hookAddress: string; + let ismFactory: HyperlaneIsmFactory; + let factories: HyperlaneContractsMap; + let erc20Factory: ERC20Test__factory; + let token: ERC20Test; + let signer: SignerWithAddress; + let multiProvider: MultiProvider; + let coreApp: TestCoreApp; + let routerConfigMap: ChainMap; + let baseConfig: RouterConfig; + + async function validateCoreValues(deployedToken: GasRouter) { + expect(await deployedToken.mailbox()).to.equal(mailbox.address); + expect(await deployedToken.hook()).to.equal(hookAddress); + expect(await deployedToken.interchainSecurityModule()).to.equal( + constants.AddressZero, + ); + expect(await deployedToken.owner()).to.equal(signer.address); + } + before(async () => { + [signer] = await hre.ethers.getSigners(); + multiProvider = MultiProvider.createTestMultiProvider({ signer }); + const ismFactoryDeployer = new HyperlaneProxyFactoryDeployer(multiProvider); + factories = await ismFactoryDeployer.deploy( + multiProvider.mapKnownChains(() => ({})), + ); + ismFactory = new HyperlaneIsmFactory(factories, multiProvider); + coreApp = await new TestCoreDeployer(multiProvider, ismFactory).deployApp(); + routerConfigMap = coreApp.getRouterConfig(signer.address); + + erc20Factory = new ERC20Test__factory(signer); + token = await erc20Factory.deploy( + TOKEN_NAME, + TOKEN_NAME, + TOKEN_SUPPLY, + TOKEN_DECIMALS, + ); + + baseConfig = routerConfigMap[chain]; + + mailbox = Mailbox__factory.connect(baseConfig.mailbox, signer); + hookAddress = await mailbox.defaultHook(); + }); + + it('should create with a a collateral config', async () => { + const config = { + ...baseConfig, + type: TokenType.collateral, + token: token.address, + hook: hookAddress, + }; + + // Deploy using WarpModule + const evmERC20WarpModule = await EvmERC20WarpModule.create({ + chain, + config, + multiProvider, + }); + + // Let's derive it's onchain token type + const { deployedTokenRoute } = evmERC20WarpModule.serialize(); + const tokenType: TokenType = + await evmERC20WarpModule.reader.deriveTokenType(deployedTokenRoute); + expect(tokenType).to.equal(TokenType.collateral); + }); + + it('should create with a collateral vault config', async () => { + const vaultFactory = new ERC4626Test__factory(signer); + const vault = await vaultFactory.deploy( + token.address, + TOKEN_NAME, + TOKEN_NAME, + ); + const config = { + type: TokenType.collateralVault, + token: vault.address, + hook: hookAddress, + ...baseConfig, + }; + + // Deploy using WarpModule + const evmERC20WarpModule = await EvmERC20WarpModule.create({ + chain, + config, + multiProvider, + }); + + // Let's derive it's onchain token type + const { deployedTokenRoute } = evmERC20WarpModule.serialize(); + const tokenType: TokenType = + await evmERC20WarpModule.reader.deriveTokenType(deployedTokenRoute); + expect(tokenType).to.equal(TokenType.collateralVault); + + // Validate onchain token values + const collateralVault = HypERC20CollateralVaultDeposit__factory.connect( + deployedTokenRoute, + signer, + ); + await validateCoreValues(collateralVault); + expect(await collateralVault.vault()).to.equal(vault.address); + expect(await collateralVault.wrappedToken()).to.equal(token.address); + }); + + it('should create with a a synthetic config', async () => { + const config = { + type: TokenType.synthetic, + token: token.address, + hook: hookAddress, + name: TOKEN_NAME, + symbol: TOKEN_NAME, + decimals: TOKEN_DECIMALS, + totalSupply: TOKEN_SUPPLY, + ...baseConfig, + }; + + // Deploy using WarpModule + const evmERC20WarpModule = await EvmERC20WarpModule.create({ + chain, + config, + multiProvider, + }); + + // Let's derive it's onchain token type + const { deployedTokenRoute } = evmERC20WarpModule.serialize(); + const tokenType: TokenType = + await evmERC20WarpModule.reader.deriveTokenType(deployedTokenRoute); + expect(tokenType).to.equal(TokenType.synthetic); + + // Validate onchain token values + const synthetic = HypERC20__factory.connect(deployedTokenRoute, signer); + await validateCoreValues(synthetic); + expect(await synthetic.name()).to.equal(TOKEN_NAME); + expect(await synthetic.symbol()).to.equal(TOKEN_NAME); + expect(await synthetic.decimals()).to.equal(TOKEN_DECIMALS); + expect(await synthetic.totalSupply()).to.equal(TOKEN_SUPPLY); + }); + + it('should create with a a native config', async () => { + const config = { + type: TokenType.native, + hook: hookAddress, + ...baseConfig, + } as TokenRouterConfig; + + // Deploy using WarpModule + const evmERC20WarpModule = await EvmERC20WarpModule.create({ + chain, + config, + multiProvider, + }); + + // Let's derive it's onchain token type + const { deployedTokenRoute } = evmERC20WarpModule.serialize(); + const tokenType: TokenType = + await evmERC20WarpModule.reader.deriveTokenType(deployedTokenRoute); + expect(tokenType).to.equal(TokenType.native); + + // Validate onchain token values + const native = HypNative__factory.connect(deployedTokenRoute, signer); + await validateCoreValues(native); + }); +}); diff --git a/typescript/sdk/src/token/EvmERC20WarpModule.ts b/typescript/sdk/src/token/EvmERC20WarpModule.ts new file mode 100644 index 000000000..a41101e79 --- /dev/null +++ b/typescript/sdk/src/token/EvmERC20WarpModule.ts @@ -0,0 +1,93 @@ +import { Address, ProtocolType, rootLogger } from '@hyperlane-xyz/utils'; + +import { + HyperlaneModule, + HyperlaneModuleParams, +} from '../core/AbstractHyperlaneModule.js'; +import { MultiProvider } from '../providers/MultiProvider.js'; +import { AnnotatedEV5Transaction } from '../providers/ProviderType.js'; +import { ChainNameOrId } from '../types.js'; + +import { EvmERC20WarpRouteReader } from './EvmERC20WarpRouteReader.js'; +import { HypERC20Deployer } from './deploy.js'; +import { TokenRouterConfig } from './schemas.js'; + +export class EvmERC20WarpModule extends HyperlaneModule< + ProtocolType.Ethereum, + TokenRouterConfig, + { + deployedTokenRoute: Address; + } +> { + protected logger = rootLogger.child({ + module: 'EvmERC20WarpModule', + }); + reader: EvmERC20WarpRouteReader; + + constructor( + protected readonly multiProvider: MultiProvider, + args: HyperlaneModuleParams< + TokenRouterConfig, + { + deployedTokenRoute: Address; + } + >, + ) { + super(args); + + this.reader = new EvmERC20WarpRouteReader(multiProvider, args.chain); + } + + /** + * Retrieves the token router configuration for the specified address. + * + * @param address - The address to derive the token router configuration from. + * @returns A promise that resolves to the token router configuration. + */ + public async read(): Promise { + return this.reader.deriveWarpRouteConfig( + this.args.addresses.deployedTokenRoute, + ); + } + + /** + * Updates the Warp Route contract with the provided configuration. + * + * @remark Currently only supports updating ISM or hook. + * + * @param expectedConfig - The configuration for the token router to be updated. + * @returns An array of Ethereum transactions that were executed to update the contract, or an error if the update failed. + */ + public async update( + _expectedConfig: TokenRouterConfig, + ): Promise { + throw Error('Not implemented'); + } + + /** + * Deploys the Warp Route. + * + * @param chain - The chain to deploy the module on. + * @param config - The configuration for the token router. + * @param multiProvider - The multi-provider instance to use. + * @returns A new instance of the EvmERC20WarpHyperlaneModule. + */ + public static async create(params: { + chain: ChainNameOrId; + config: TokenRouterConfig; + multiProvider: MultiProvider; + }): Promise { + const { chain, config, multiProvider } = params; + const chainName = multiProvider.getChainName(chain); + const deployer = new HypERC20Deployer(multiProvider); + const deployedContracts = await deployer.deployContracts(chainName, config); + + return new EvmERC20WarpModule(multiProvider, { + addresses: { + deployedTokenRoute: deployedContracts[config.type].address, + }, + chain, + config, + }); + } +} diff --git a/typescript/sdk/src/token/EvmERC20WarpRouteReader.hardhat-test.ts b/typescript/sdk/src/token/EvmERC20WarpRouteReader.hardhat-test.ts new file mode 100644 index 000000000..dc791a2d6 --- /dev/null +++ b/typescript/sdk/src/token/EvmERC20WarpRouteReader.hardhat-test.ts @@ -0,0 +1,237 @@ +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers.js'; +import { expect } from 'chai'; +import hre from 'hardhat'; + +import { + ERC20Test, + ERC20Test__factory, + ERC4626, + ERC4626Test__factory, + Mailbox, + Mailbox__factory, +} from '@hyperlane-xyz/core'; +import { + HyperlaneContractsMap, + RouterConfig, + TestChainName, + TokenRouterConfig, +} from '@hyperlane-xyz/sdk'; + +import { TestCoreApp } from '../core/TestCoreApp.js'; +import { TestCoreDeployer } from '../core/TestCoreDeployer.js'; +import { HyperlaneProxyFactoryDeployer } from '../deploy/HyperlaneProxyFactoryDeployer.js'; +import { ProxyFactoryFactories } from '../deploy/contracts.js'; +import { HyperlaneIsmFactory } from '../ism/HyperlaneIsmFactory.js'; +import { MultiProvider } from '../providers/MultiProvider.js'; +import { ChainMap } from '../types.js'; + +import { EvmERC20WarpRouteReader } from './EvmERC20WarpRouteReader.js'; +import { TokenType } from './config.js'; +import { HypERC20Deployer } from './deploy.js'; + +describe('ERC20WarpRouterReader', async () => { + const TOKEN_NAME = 'fake'; + const TOKEN_SUPPLY = '100000000000000000000'; + const TOKEN_DECIMALS = 18; + const GAS = 65_000; + const chain = TestChainName.test4; + let ismFactory: HyperlaneIsmFactory; + let factories: HyperlaneContractsMap; + let erc20Factory: ERC20Test__factory; + let token: ERC20Test; + let signer: SignerWithAddress; + let deployer: HypERC20Deployer; + let multiProvider: MultiProvider; + let coreApp: TestCoreApp; + let routerConfigMap: ChainMap; + let baseConfig: RouterConfig; + let mailbox: Mailbox; + let evmERC20WarpRouteReader: EvmERC20WarpRouteReader; + let vault: ERC4626; + before(async () => { + [signer] = await hre.ethers.getSigners(); + multiProvider = MultiProvider.createTestMultiProvider({ signer }); + const ismFactoryDeployer = new HyperlaneProxyFactoryDeployer(multiProvider); + factories = await ismFactoryDeployer.deploy( + multiProvider.mapKnownChains(() => ({})), + ); + ismFactory = new HyperlaneIsmFactory(factories, multiProvider); + coreApp = await new TestCoreDeployer(multiProvider, ismFactory).deployApp(); + routerConfigMap = coreApp.getRouterConfig(signer.address); + + erc20Factory = new ERC20Test__factory(signer); + token = await erc20Factory.deploy( + TOKEN_NAME, + TOKEN_NAME, + TOKEN_SUPPLY, + TOKEN_DECIMALS, + ); + + baseConfig = routerConfigMap[chain]; + mailbox = Mailbox__factory.connect(baseConfig.mailbox, signer); + evmERC20WarpRouteReader = new EvmERC20WarpRouteReader(multiProvider, chain); + deployer = new HypERC20Deployer(multiProvider); + + const vaultFactory = new ERC4626Test__factory(signer); + vault = await vaultFactory.deploy(token.address, TOKEN_NAME, TOKEN_NAME); + }); + + it('should derive a token type from contract', async () => { + const typesToDerive = [ + TokenType.collateral, + TokenType.collateralVault, + TokenType.synthetic, + TokenType.native, + ] as const; + + await Promise.all( + typesToDerive.map(async (type) => { + // Create config + const config = { + [chain]: { + type, + token: + type === TokenType.collateralVault + ? vault.address + : token.address, + hook: await mailbox.defaultHook(), + name: TOKEN_NAME, + symbol: TOKEN_NAME, + decimals: TOKEN_DECIMALS, + totalSupply: TOKEN_SUPPLY, + gas: GAS, + ...baseConfig, + }, + }; + // Deploy warp route with config + const warpRoute = await deployer.deploy(config); + const derivedTokenType = await evmERC20WarpRouteReader.deriveTokenType( + warpRoute[chain][type].address, + ); + expect(derivedTokenType).to.equal(type); + }), + ); + }); + + it('should derive collateral config correctly', async () => { + // Create config + const config = { + [chain]: { + type: TokenType.collateral, + token: token.address, + hook: await mailbox.defaultHook(), + interchainsecurityModule: await mailbox.defaultIsm(), + ...baseConfig, + }, + }; + // Deploy with config + const warpRoute = await deployer.deploy(config); + + // Derive config and check if each value matches + const derivedConfig = await evmERC20WarpRouteReader.deriveWarpRouteConfig( + warpRoute[chain].collateral.address, + ); + for (const [key, value] of Object.entries(derivedConfig)) { + const deployedValue = (config[chain] as any)[key]; + if (deployedValue && typeof value === 'string') + expect(deployedValue).to.equal(value); + } + + // Check hook because they're potentially objects + expect(derivedConfig.hook).to.deep.equal( + await evmERC20WarpRouteReader.evmHookReader.deriveHookConfig( + config[chain].hook as string, + ), + ); + // Check ism. should return undefined + expect(derivedConfig.interchainSecurityModule).to.be.undefined; + + // Check if token values matches + if (derivedConfig.type === TokenType.collateral) { + expect(derivedConfig.name).to.equal(TOKEN_NAME); + expect(derivedConfig.symbol).to.equal(TOKEN_NAME); + expect(derivedConfig.decimals).to.equal(TOKEN_DECIMALS); + } + }); + + it('should derive synthetic config correctly', async () => { + // Create config + const config = { + [chain]: { + type: TokenType.synthetic, + token: token.address, + hook: await mailbox.defaultHook(), + name: TOKEN_NAME, + symbol: TOKEN_NAME, + decimals: TOKEN_DECIMALS, + totalSupply: TOKEN_SUPPLY, + ...baseConfig, + }, + }; + // Deploy with config + const warpRoute = await deployer.deploy(config); + + // Derive config and check if each value matches + const derivedConfig = await evmERC20WarpRouteReader.deriveWarpRouteConfig( + warpRoute[chain].synthetic.address, + ); + for (const [key, value] of Object.entries(derivedConfig)) { + const deployedValue = (config[chain] as any)[key]; + if (deployedValue && typeof value === 'string') + expect(deployedValue).to.equal(value); + } + + // Check if token values matches + if (derivedConfig.type === TokenType.collateral) { + expect(derivedConfig.name).to.equal(TOKEN_NAME); + expect(derivedConfig.symbol).to.equal(TOKEN_NAME); + } + }); + + it('should derive native config correctly', async () => { + // Create config + const config = { + [chain]: { + type: TokenType.native, + hook: await mailbox.defaultHook(), + ...baseConfig, + }, + } as ChainMap; + // Deploy with config + const warpRoute = await deployer.deploy(config); + + // Derive config and check if each value matches + const derivedConfig = await evmERC20WarpRouteReader.deriveWarpRouteConfig( + warpRoute[chain].native.address, + ); + for (const [key, value] of Object.entries(derivedConfig)) { + const deployedValue = (config[chain] as any)[key]; + if (deployedValue && typeof value === 'string') + expect(deployedValue).to.equal(value); + } + + // Check if token values matches + expect(derivedConfig.decimals).to.equal(TOKEN_DECIMALS); + }); + + it('should return undefined if ism is not set onchain', async () => { + // Create config + const config = { + [chain]: { + type: TokenType.collateral, + token: token.address, + hook: await mailbox.defaultHook(), + ...baseConfig, + }, + }; + // Deploy with config + const warpRoute = await deployer.deploy(config); + + // Derive config and check if each value matches + const derivedConfig = await evmERC20WarpRouteReader.deriveWarpRouteConfig( + warpRoute[chain].collateral.address, + ); + + expect(derivedConfig.interchainSecurityModule).to.be.undefined; + }); +}); diff --git a/typescript/sdk/src/token/EvmERC20WarpRouteReader.ts b/typescript/sdk/src/token/EvmERC20WarpRouteReader.ts index 3055ce4bf..fc7a5326c 100644 --- a/typescript/sdk/src/token/EvmERC20WarpRouteReader.ts +++ b/typescript/sdk/src/token/EvmERC20WarpRouteReader.ts @@ -1,33 +1,37 @@ -import { ethers, providers } from 'ethers'; +import { BigNumber, constants, providers } from 'ethers'; import { - ERC20__factory, + HypERC20CollateralVaultDeposit__factory, HypERC20Collateral__factory, - MailboxClient__factory, + HypERC20__factory, } from '@hyperlane-xyz/core'; -import { Address, eqAddress } from '@hyperlane-xyz/utils'; +import { + MailboxClientConfig, + TokenRouterConfig, + TokenType, +} from '@hyperlane-xyz/sdk'; +import { Address, eqAddress, rootLogger } from '@hyperlane-xyz/utils'; import { DEFAULT_CONTRACT_READ_CONCURRENCY } from '../consts/concurrency.js'; import { EvmHookReader } from '../hook/EvmHookReader.js'; import { EvmIsmReader } from '../ism/EvmIsmReader.js'; import { MultiProvider } from '../providers/MultiProvider.js'; -import { MailboxClientConfig } from '../router/types.js'; -import { ChainName } from '../types.js'; +import { ChainNameOrId } from '../types.js'; -import { TokenType } from './config.js'; -import { TokenRouterConfig } from './schemas.js'; +import { CollateralExtensions } from './config.js'; import { TokenMetadata } from './types.js'; -const { AddressZero } = ethers.constants; - export class EvmERC20WarpRouteReader { + protected readonly logger = rootLogger.child({ + module: 'EvmERC20WarpRouteReader', + }); provider: providers.Provider; evmHookReader: EvmHookReader; evmIsmReader: EvmIsmReader; constructor( protected readonly multiProvider: MultiProvider, - protected readonly chain: ChainName, + protected readonly chain: ChainNameOrId, protected readonly concurrency: number = DEFAULT_CONTRACT_READ_CONCURRENCY, ) { this.provider = this.multiProvider.getProvider(chain); @@ -38,50 +42,91 @@ export class EvmERC20WarpRouteReader { /** * Derives the configuration for a Hyperlane ERC20 router contract at the given address. * - * @param address - The address of the Hyperlane ERC20 router contract. + * @param warpRouteAddress - The address of the Hyperlane ERC20 router contract. * @returns The configuration for the Hyperlane ERC20 router. * */ async deriveWarpRouteConfig( - address: Address, - type = TokenType.collateral, + warpRouteAddress: Address, ): Promise { - const mailboxClientConfig = await this.fetchMailboxClientConfig(address); - - let token: Address; - switch (type) { - case TokenType.collateral: - token = await HypERC20Collateral__factory.connect( - address, - this.provider, - ).wrappedToken(); - break; - case TokenType.synthetic: - token = address; - break; - default: - throw new Error(`Invalid token type: ${type}`); - } - const fetchedTokenMetadata = await this.fetchTokenMetadata(token); + // Derive the config type + const type = await this.deriveTokenType(warpRouteAddress); + const fetchedBaseMetadata = await this.fetchMailboxClientConfig( + warpRouteAddress, + ); + const fetchedTokenMetadata = await this.fetchTokenMetadata( + type, + warpRouteAddress, + ); return { - type, - token: TokenType.collateral === type ? token : undefined, - ...mailboxClientConfig, + ...fetchedBaseMetadata, ...fetchedTokenMetadata, + type, } as TokenRouterConfig; } + /** + * Derives the token type for a given Warp Route address using specific methods + * + * @param warpRouteAddress - The Warp Route address to derive the token type for. + * @returns The derived token type, which can be one of: collateralVault, collateral, native, or synthetic. + */ + async deriveTokenType(warpRouteAddress: Address): Promise { + const contractTypes: Partial< + Record + > = { + collateralVault: { + factory: HypERC20CollateralVaultDeposit__factory, + method: 'vault', + }, + collateral: { + factory: HypERC20Collateral__factory, + method: 'wrappedToken', + }, + synthetic: { + factory: HypERC20__factory, + method: 'decimals', + }, + }; + + // First, try checking token specific methods + for (const [type, { factory, method }] of Object.entries(contractTypes)) { + try { + const warpRoute = factory.connect(warpRouteAddress, this.provider); + await warpRoute[method](); + return type as TokenType; + } catch (e) { + continue; + } + } + + // Finally check native + // Using estimateGas to send 1 wei. Success implies that the Warp Route has a receive() function + try { + await this.multiProvider.estimateGas(this.chain, { + to: warpRouteAddress, + from: await this.multiProvider.getSignerAddress(this.chain), + value: BigNumber.from(1), + }); + return TokenType.native; + } catch (e) { + throw Error( + `Error accessing token specific method, implying this is not a supported token.`, + ); + } + } + /** * Fetches the base metadata for a Warp Route contract. * * @param routerAddress - The address of the Warp Route contract. - * @returns The base metadata for the Warp Route contract, including the mailbox, owner, wrapped token address, hook, and interchain security module. + * @returns The base metadata for the Warp Route contract, including the mailbox, owner, hook, and ism. */ async fetchMailboxClientConfig( routerAddress: Address, ): Promise { - const warpRoute = MailboxClient__factory.connect( + const warpRoute = HypERC20Collateral__factory.connect( routerAddress, this.provider, ); @@ -92,10 +137,10 @@ export class EvmERC20WarpRouteReader { warpRoute.interchainSecurityModule(), ]); - const derivedIsm = eqAddress(ism, AddressZero) + const derivedIsm = eqAddress(ism, constants.AddressZero) ? undefined : await this.evmIsmReader.deriveIsmConfig(ism); - const derivedHook = eqAddress(hook, AddressZero) + const derivedHook = eqAddress(hook, constants.AddressZero) ? undefined : await this.evmHookReader.deriveHookConfig(hook); @@ -112,16 +157,50 @@ export class EvmERC20WarpRouteReader { * * @param tokenAddress - The address of the token. * @returns A partial ERC20 metadata object containing the token name, symbol, total supply, and decimals. + * Throws if unsupported token type */ - async fetchTokenMetadata(tokenAddress: Address): Promise { - const erc20 = ERC20__factory.connect(tokenAddress, this.provider); - const [name, symbol, totalSupply, decimals] = await Promise.all([ + async fetchTokenMetadata( + type: TokenType, + tokenAddress: Address, + ): Promise { + if (CollateralExtensions.includes(type)) { + const erc20 = HypERC20Collateral__factory.connect( + tokenAddress, + this.provider, + ); + const token = await erc20.wrappedToken(); + const { name, symbol, decimals, totalSupply } = + await this.fetchERC20Metadata(token); + + return { name, symbol, decimals, totalSupply, token }; + } else if (type === TokenType.synthetic) { + return this.fetchERC20Metadata(tokenAddress); + } else if (type === TokenType.native) { + const chainMetadata = this.multiProvider.getChainMetadata(this.chain); + if (chainMetadata.nativeToken) { + const { name, symbol, decimals } = chainMetadata.nativeToken; + return { name, symbol, decimals, totalSupply: 0 }; + } else { + throw new Error( + `Warp route config specifies native token but chain metadata for ${this.chain} does not provide native token details`, + ); + } + } else { + throw new Error( + `Unsupported token type ${type} when fetching token metadata`, + ); + } + } + + async fetchERC20Metadata(tokenAddress: Address): Promise { + const erc20 = HypERC20__factory.connect(tokenAddress, this.provider); + const [name, symbol, decimals, totalSupply] = await Promise.all([ erc20.name(), erc20.symbol(), - erc20.totalSupply(), erc20.decimals(), + erc20.totalSupply(), ]); - return { name, symbol, totalSupply: totalSupply.toString(), decimals }; + return { name, symbol, decimals, totalSupply: totalSupply.toString() }; } } diff --git a/typescript/sdk/src/token/config.ts b/typescript/sdk/src/token/config.ts index a68b2a5a9..f12d1e77a 100644 --- a/typescript/sdk/src/token/config.ts +++ b/typescript/sdk/src/token/config.ts @@ -13,6 +13,11 @@ export enum TokenType { nativeScaled = 'nativeScaled', } +export const CollateralExtensions = [ + TokenType.collateral, + TokenType.collateralVault, +]; + export const gasOverhead = (tokenType: TokenType) => { switch (tokenType) { case TokenType.fastSynthetic: diff --git a/typescript/sdk/src/token/deploy.hardhat-test.ts b/typescript/sdk/src/token/deploy.hardhat-test.ts index 71ac68287..772813943 100644 --- a/typescript/sdk/src/token/deploy.hardhat-test.ts +++ b/typescript/sdk/src/token/deploy.hardhat-test.ts @@ -91,12 +91,9 @@ describe('TokenDeployer', async () => { routerAddress = warpRoute[chain][type].address; }); - it(`should derive TokenRouterConfig from ${type} correctly`, async () => { - const derivedConfig = await reader.deriveWarpRouteConfig( - routerAddress, - type, - ); - expect(derivedConfig).to.include(config[chain]); + it(`should derive TokenRouterConfig correctly`, async () => { + const derivedConfig = await reader.deriveWarpRouteConfig(routerAddress); + expect(derivedConfig.type).to.equal(config[chain].type); }); }); } From c38f799afa04b91cd4a187634129b502239f27e2 Mon Sep 17 00:00:00 2001 From: Paul Balaji Date: Thu, 6 Jun 2024 14:12:30 +0100 Subject: [PATCH 24/73] ci: cherry-pick merge queue update (#3914) bringing over https://github.com/hyperlane-xyz/hyperlane-monorepo/pull/3913 --- .github/workflows/rust-skipped.yml | 2 ++ .github/workflows/rust.yml | 3 ++- .github/workflows/test.yml | 6 ++++-- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/rust-skipped.yml b/.github/workflows/rust-skipped.yml index b6e6c51cd..2d9fe8a5d 100644 --- a/.github/workflows/rust-skipped.yml +++ b/.github/workflows/rust-skipped.yml @@ -9,6 +9,8 @@ on: paths-ignore: - 'rust/**' - .github/workflows/rust.yml + # Support for merge queues + merge_group: env: CARGO_TERM_COLOR: always diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 48caa1f77..a6e270a48 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -6,7 +6,8 @@ on: paths: - 'rust/**' - .github/workflows/rust.yml - + # Support for merge queues + merge_group: # Allows you to run this workflow manually from the Actions tab workflow_dispatch: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e56f11f58..7f4f8c9cb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,6 +7,8 @@ on: pull_request: branches: - '*' # run against all branches + # Support for merge queues + merge_group: # Allows you to run this workflow manually from the Actions tab workflow_dispatch: @@ -224,7 +226,7 @@ jobs: e2e-matrix: runs-on: larger-runner - if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.base_ref == 'main') + if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.base_ref == 'main') || github.event_name == 'merge_group' needs: [yarn-build, checkout-registry] strategy: matrix: @@ -325,7 +327,7 @@ jobs: cli-e2e: runs-on: larger-runner - if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.base_ref == 'main' || github.base_ref == 'cli-2.0') + if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.base_ref == 'main' || github.base_ref == 'cli-2.0') || github.event_name == 'merge_group' needs: [yarn-build, checkout-registry] strategy: matrix: From 845ed5796ef2005013f1bf2e0cd778481acaf73b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Noah=20Bayindirli=20=F0=9F=A5=82?= Date: Thu, 6 Jun 2024 10:46:59 -0400 Subject: [PATCH 25/73] feat(cli): add 'source' to detectAndConfirmOrPrompt (#3906) ### Description - adds `source` field to detect and confirm prompt so users know where defaults are coming from ### Drive-by changes - none ### Related issues - none ### Backward compatibility - yes ### Testing - manual --- typescript/cli/src/commands/core.ts | 1 + typescript/cli/src/config/chain.ts | 3 +++ typescript/cli/src/config/ism.ts | 1 + typescript/cli/src/config/warp.ts | 2 ++ typescript/cli/src/utils/chains.ts | 5 ++++- 5 files changed, 11 insertions(+), 1 deletion(-) diff --git a/typescript/cli/src/commands/core.ts b/typescript/cli/src/commands/core.ts index a8da2db49..f62ff53f8 100644 --- a/typescript/cli/src/commands/core.ts +++ b/typescript/cli/src/commands/core.ts @@ -60,6 +60,7 @@ export const configure: CommandModuleWithContext<{ async () => context.signer?.getAddress(), 'Enter the desired', 'owner address', + 'signer', ); // Create default Ism config (advanced or trusted) diff --git a/typescript/cli/src/config/chain.ts b/typescript/cli/src/config/chain.ts index c2655cab3..5c6e756cc 100644 --- a/typescript/cli/src/config/chain.ts +++ b/typescript/cli/src/config/chain.ts @@ -48,6 +48,7 @@ export async function createChainConfig({ }, 'Enter http or https', 'rpc url', + 'JSON RPC provider', ); const provider = new ethers.providers.JsonRpcProvider(rpcUrl); @@ -60,6 +61,7 @@ export async function createChainConfig({ }, 'Enter (one word, lower case)', 'chain name', + 'JSON RPC provider', ); const chainId = parseInt( @@ -70,6 +72,7 @@ export async function createChainConfig({ }, 'Enter a (number)', 'chain id', + 'JSON RPC provider', ), 10, ); diff --git a/typescript/cli/src/config/ism.ts b/typescript/cli/src/config/ism.ts index d3d90971f..09a1b00d5 100644 --- a/typescript/cli/src/config/ism.ts +++ b/typescript/cli/src/config/ism.ts @@ -135,6 +135,7 @@ export async function createTrustedRelayerConfig( async () => context.signer?.getAddress(), 'For trusted relayer ISM, enter', 'relayer address', + 'signer', ); return { type: IsmType.TRUSTED_RELAYER, diff --git a/typescript/cli/src/config/warp.ts b/typescript/cli/src/config/warp.ts index 974c219ba..3f18c0ed5 100644 --- a/typescript/cli/src/config/warp.ts +++ b/typescript/cli/src/config/warp.ts @@ -114,6 +114,7 @@ export async function createWarpRouteDeployConfig({ async () => context.signer?.getAddress(), 'Enter the desired', 'owner address', + 'signer', ); const warpChains = await runMultiChainSelectionStep( @@ -140,6 +141,7 @@ export async function createWarpRouteDeployConfig({ }, `For ${chain}, enter the`, 'mailbox address', + 'hyperlane-registry', ); const interchainSecurityModule = shouldUseDefault diff --git a/typescript/cli/src/utils/chains.ts b/typescript/cli/src/utils/chains.ts index 3c0e7477d..8892ce747 100644 --- a/typescript/cli/src/utils/chains.ts +++ b/typescript/cli/src/utils/chains.ts @@ -78,13 +78,16 @@ export async function detectAndConfirmOrPrompt( detect: () => Promise, prompt: string, label: string, + source?: string, ): Promise { let detectedValue: string | undefined; try { detectedValue = await detect(); if (detectedValue) { const confirmed = await confirm({ - message: `Detected ${label} as ${detectedValue}, is this correct?`, + message: `Detected ${label} as ${detectedValue}${ + source ? ` from ${source}` : '' + }, is this correct?`, }); if (confirmed) { return detectedValue; From 63367c55046e1e778d79f03531b3e18d7c9aad4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Noah=20Bayindirli=20=F0=9F=A5=82?= Date: Thu, 6 Jun 2024 12:05:47 -0400 Subject: [PATCH 26/73] fix(cli): remove opStackIsm & pausableIsm from hyperlane core configure (#3908) ### Description - removes `opStackIsm` & `pausableIsm` from `hyperlane core configure` ### Drive-by changes - none ### Related issues - fixes https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/3907 ### Backward compatibility - yes ### Testing - ci-test - manual --- typescript/cli/src/config/ism.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/typescript/cli/src/config/ism.ts b/typescript/cli/src/config/ism.ts index 09a1b00d5..7ec1b6699 100644 --- a/typescript/cli/src/config/ism.ts +++ b/typescript/cli/src/config/ism.ts @@ -71,8 +71,6 @@ const ISM_TYPE_DESCRIPTIONS: Record = { [IsmType.TRUSTED_RELAYER]: 'Deliver messages from an authorized address', [IsmType.TEST_ISM]: 'ISM where you can deliver messages without any validation (WARNING: only for testing, do not use in production)', - [IsmType.OP_STACK]: '', - [IsmType.PAUSABLE]: '', }; export async function createIsmConfig( @@ -80,10 +78,12 @@ export async function createIsmConfig( ): Promise { const moduleType = await select({ message: 'Select ISM type', - choices: Object.values(IsmType).map((value) => ({ - value, - description: ISM_TYPE_DESCRIPTIONS[value], - })), + choices: Object.entries(ISM_TYPE_DESCRIPTIONS).map( + ([value, description]) => ({ + value, + description, + }), + ), pageSize: 10, }); @@ -105,7 +105,7 @@ export async function createIsmConfig( return createTrustedRelayerConfig(context); } - throw new Error(`Invalid ISM type: ${moduleType}}`); + throw new Error(`Invalid ISM type: ${moduleType}`); } export async function createMultisigConfig( From 6db9fa9adae8aec3c22046e8d3e91b8deee8b81b Mon Sep 17 00:00:00 2001 From: Lee <6251863+ltyu@users.noreply.github.com> Date: Thu, 6 Jun 2024 17:29:44 -0400 Subject: [PATCH 27/73] feat: implement `hyperlane warp deploy` (#3920) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Description - Update `hyperlane warp deploy` command ### Drive-by changes - Move Core deploy to core.ts - Update Module return args to match TokenType ### Related issues - Fixes #[3541](https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/3541) ### Backward compatibility yes, but old command no longer applies ### Testing Manual --------- Signed-off-by: Paul Balaji Co-authored-by: Paul Balaji Co-authored-by: Yorke Rhodes Co-authored-by: J M Rossy Co-authored-by: Noah Bayindirli 🥂 Co-authored-by: Noah Bayindirli 🥂 --- .changeset/slimy-toys-argue.md | 6 ++ typescript/cli/ci-test.sh | 6 +- typescript/cli/src/commands/core.ts | 59 +++++++++++- typescript/cli/src/commands/deploy.ts | 94 +------------------ typescript/cli/src/commands/warp.ts | 35 +++++++ typescript/sdk/src/index.ts | 2 + .../token/EvmERC20WarpModule.hardhat-test.ts | 37 +++++--- .../sdk/src/token/EvmERC20WarpModule.ts | 1 - 8 files changed, 126 insertions(+), 114 deletions(-) create mode 100644 .changeset/slimy-toys-argue.md diff --git a/.changeset/slimy-toys-argue.md b/.changeset/slimy-toys-argue.md new file mode 100644 index 000000000..7b9f760bb --- /dev/null +++ b/.changeset/slimy-toys-argue.md @@ -0,0 +1,6 @@ +--- +'@hyperlane-xyz/cli': minor +'@hyperlane-xyz/sdk': minor +--- + +Implement hyperlane warp deploy diff --git a/typescript/cli/ci-test.sh b/typescript/cli/ci-test.sh index 601a56c6c..3cdbcdf09 100755 --- a/typescript/cli/ci-test.sh +++ b/typescript/cli/ci-test.sh @@ -162,7 +162,7 @@ run_hyperlane_deploy_warp_dry_run() { update_deployer_balance; echo -e "\nDry-running warp route deployments to Alfajores" - yarn workspace @hyperlane-xyz/cli run hyperlane deploy warp \ + yarn workspace @hyperlane-xyz/cli run hyperlane warp deploy \ --dry-run alfajores \ --overrides ${TEST_CONFIGS_PATH}/dry-run \ --config ${TEST_CONFIGS_PATH}/dry-run/warp-route-deployment.yaml \ @@ -200,7 +200,7 @@ run_hyperlane_deploy_warp() { update_deployer_balance; echo -e "\nDeploying hypNative warp route" - yarn workspace @hyperlane-xyz/cli run hyperlane deploy warp \ + yarn workspace @hyperlane-xyz/cli run hyperlane warp deploy \ --registry $REGISTRY_PATH \ --overrides " " \ --config $WARP_DEPLOY_CONFIG_PATH \ @@ -213,7 +213,7 @@ run_hyperlane_deploy_warp() { /tmp/warp-collateral-deployment.json \ echo "Deploying hypCollateral warp route" - yarn workspace @hyperlane-xyz/cli run hyperlane deploy warp \ + yarn workspace @hyperlane-xyz/cli run hyperlane warp deploy \ --registry $REGISTRY_PATH \ --overrides " " \ --config /tmp/warp-collateral-deployment.json \ diff --git a/typescript/cli/src/commands/core.ts b/typescript/cli/src/commands/core.ts index f62ff53f8..56e2c3949 100644 --- a/typescript/cli/src/commands/core.ts +++ b/typescript/cli/src/commands/core.ts @@ -4,7 +4,12 @@ import { CoreConfigSchema, EvmCoreReader, IsmConfig } from '@hyperlane-xyz/sdk'; import { createHookConfig } from '../config/hooks.js'; import { createIsmConfig, createTrustedRelayerConfig } from '../config/ism.js'; -import { CommandModuleWithContext } from '../context/types.js'; +import { + CommandModuleWithContext, + CommandModuleWithWriteContext, +} from '../context/types.js'; +import { runCoreDeploy } from '../deploy/core.js'; +import { evaluateIfDryRunFailure } from '../deploy/dry-run.js'; import { log, logBlue, @@ -13,10 +18,14 @@ import { logRed, } from '../logger.js'; import { detectAndConfirmOrPrompt } from '../utils/chains.js'; -import { writeYamlOrJson } from '../utils/files.js'; +import { readYamlOrJson, writeYamlOrJson } from '../utils/files.js'; -import { deploy } from './deploy.js'; -import { chainCommandOption, outputFileCommandOption } from './options.js'; +import { + chainCommandOption, + dryRunCommandOption, + fromAddressCommandOption, + outputFileCommandOption, +} from './options.js'; /** * Parent command @@ -34,6 +43,48 @@ export const coreCommand: CommandModule = { handler: () => log('Command required'), }; +/** + * Generates a command module for deploying Hyperlane contracts, given a command + * + * @param commandName - the deploy command key used to look up the deployFunction + * @returns A command module used to deploy Hyperlane contracts. + */ +export const deploy: CommandModuleWithWriteContext<{ + chain: string; + config: string; + dryRun: string; + fromAddress: string; +}> = { + command: 'deploy', + describe: 'Deploy Hyperlane contracts', + builder: { + chain: chainCommandOption, + config: outputFileCommandOption( + './configs/core-config.yaml', + false, + 'The path to a JSON or YAML file with a core deployment config.', + ), + 'dry-run': dryRunCommandOption, + 'from-address': fromAddressCommandOption, + }, + handler: async ({ context, chain, config: configFilePath, dryRun }) => { + logGray(`Hyperlane permissionless deployment${dryRun ? ' dry-run' : ''}`); + logGray(`------------------------------------------------`); + + try { + await runCoreDeploy({ + context, + chain, + config: readYamlOrJson(configFilePath), + }); + } catch (error: any) { + evaluateIfDryRunFailure(error, dryRun); + throw error; + } + process.exit(0); + }, +}; + export const configure: CommandModuleWithContext<{ ismAdvanced: boolean; config: string; diff --git a/typescript/cli/src/commands/deploy.ts b/typescript/cli/src/commands/deploy.ts index 74a20ac3e..aa73f1899 100644 --- a/typescript/cli/src/commands/deploy.ts +++ b/typescript/cli/src/commands/deploy.ts @@ -1,25 +1,13 @@ import { CommandModule } from 'yargs'; -import { - CommandModuleWithContext, - CommandModuleWithWriteContext, -} from '../context/types.js'; +import { CommandModuleWithContext } from '../context/types.js'; import { runKurtosisAgentDeploy } from '../deploy/agent.js'; -import { runCoreDeploy } from '../deploy/core.js'; -import { evaluateIfDryRunFailure } from '../deploy/dry-run.js'; -import { runWarpRouteDeploy } from '../deploy/warp.js'; import { log, logGray } from '../logger.js'; -import { readYamlOrJson } from '../utils/files.js'; import { agentConfigCommandOption, agentTargetsCommandOption, - chainCommandOption, - dryRunCommandOption, - fromAddressCommandOption, originCommandOption, - outputFileCommandOption, - warpDeploymentConfigCommandOption, } from './options.js'; /** @@ -29,11 +17,7 @@ export const deployCommand: CommandModule = { command: 'deploy', describe: 'Permissionlessly deploy a Hyperlane contracts or extensions', builder: (yargs) => - yargs - .command(warpCommand) - .command(agentCommand) - .version(false) - .demandCommand(), + yargs.command(agentCommand).version(false).demandCommand(), handler: () => log('Command required'), }; @@ -64,77 +48,3 @@ const agentCommand: CommandModuleWithContext<{ process.exit(0); }, }; - -/** - * Generates a command module for deploying Hyperlane contracts, given a command - * - * @param commandName - the deploy command key used to look up the deployFunction - * @returns A command module used to deploy Hyperlane contracts. - */ -export const deploy: CommandModuleWithWriteContext<{ - chain: string; - config: string; - dryRun: string; - fromAddress: string; -}> = { - command: 'deploy', - describe: 'Deploy Hyperlane contracts', - builder: { - chain: chainCommandOption, - config: outputFileCommandOption( - './configs/core-config.yaml', - false, - 'The path to a JSON or YAML file with a core deployment config.', - ), - 'dry-run': dryRunCommandOption, - 'from-address': fromAddressCommandOption, - }, - handler: async ({ context, chain, config: configFilePath, dryRun }) => { - logGray(`Hyperlane permissionless deployment${dryRun ? ' dry-run' : ''}`); - logGray(`------------------------------------------------`); - - try { - await runCoreDeploy({ - context, - chain, - config: readYamlOrJson(configFilePath), - }); - } catch (error: any) { - evaluateIfDryRunFailure(error, dryRun); - throw error; - } - process.exit(0); - }, -}; - -/** - * Warp command - */ -const warpCommand: CommandModuleWithWriteContext<{ - config: string; - 'dry-run': string; - 'from-address': string; -}> = { - command: 'warp', - describe: 'Deploy Warp Route contracts', - builder: { - config: warpDeploymentConfigCommandOption, - 'dry-run': dryRunCommandOption, - 'from-address': fromAddressCommandOption, - }, - handler: async ({ context, config, dryRun }) => { - logGray(`Hyperlane warp route deployment${dryRun ? ' dry-run' : ''}`); - logGray('------------------------------------------------'); - - try { - await runWarpRouteDeploy({ - context, - warpRouteDeploymentConfigPath: config, - }); - } catch (error: any) { - evaluateIfDryRunFailure(error, dryRun); - throw error; - } - process.exit(0); - }, -}; diff --git a/typescript/cli/src/commands/warp.ts b/typescript/cli/src/commands/warp.ts index 5a430c619..309485def 100644 --- a/typescript/cli/src/commands/warp.ts +++ b/typescript/cli/src/commands/warp.ts @@ -7,6 +7,8 @@ import { CommandModuleWithContext, CommandModuleWithWriteContext, } from '../context/types.js'; +import { evaluateIfDryRunFailure } from '../deploy/dry-run.js'; +import { runWarpRouteDeploy } from '../deploy/warp.js'; import { log, logGray, logGreen } from '../logger.js'; import { sendTestTransfer } from '../send/transfer.js'; import { writeFileAtPath } from '../utils/files.js'; @@ -14,8 +16,11 @@ import { writeFileAtPath } from '../utils/files.js'; import { addressCommandOption, chainCommandOption, + dryRunCommandOption, + fromAddressCommandOption, outputFileCommandOption, warpCoreConfigCommandOption, + warpDeploymentConfigCommandOption, } from './options.js'; import { MessageOptionsArgTypes, messageOptions } from './send.js'; @@ -28,6 +33,7 @@ export const warpCommand: CommandModule = { builder: (yargs) => yargs .command(configure) + .command(deploy) .command(read) .command(send) .version(false) @@ -36,6 +42,35 @@ export const warpCommand: CommandModule = { handler: () => log('Command required'), }; +export const deploy: CommandModuleWithWriteContext<{ + config: string; + 'dry-run': string; + 'from-address': string; +}> = { + command: 'deploy', + describe: 'Deploy Warp Route contracts', + builder: { + config: warpDeploymentConfigCommandOption, + 'dry-run': dryRunCommandOption, + 'from-address': fromAddressCommandOption, + }, + handler: async ({ context, config, dryRun }) => { + logGray(`Hyperlane warp route deployment${dryRun ? ' dry-run' : ''}`); + logGray('------------------------------------------------'); + + try { + await runWarpRouteDeploy({ + context, + warpRouteDeploymentConfigPath: config, + }); + } catch (error: any) { + evaluateIfDryRunFailure(error, dryRun); + throw error; + } + process.exit(0); + }, +}; + export const configure: CommandModuleWithContext<{ ismAdvanced: boolean; out: string; diff --git a/typescript/sdk/src/index.ts b/typescript/sdk/src/index.ts index 3a1499986..c1afe0494 100644 --- a/typescript/sdk/src/index.ts +++ b/typescript/sdk/src/index.ts @@ -447,6 +447,7 @@ export { HypERC20App } from './token/app.js'; export { HypERC20Checker } from './token/checker.js'; export { TokenType } from './token/config.js'; export { + hypERC20factories, HypERC20Factories, HypERC721Factories, TokenFactories, @@ -505,3 +506,4 @@ export { S3Config, S3Wrapper, S3Receipt } from './aws/s3.js'; export { canProposeSafeTransactions, getSafe, getSafeDelegates, getSafeService } from './utils/gnosisSafe.js'; export { EvmCoreModule, DeployedCoreAdresses } from './core/EvmCoreModule.js'; +export { EvmERC20WarpModule } from './token/EvmERC20WarpModule.js'; diff --git a/typescript/sdk/src/token/EvmERC20WarpModule.hardhat-test.ts b/typescript/sdk/src/token/EvmERC20WarpModule.hardhat-test.ts index bf64cfa17..a20ce71e3 100644 --- a/typescript/sdk/src/token/EvmERC20WarpModule.hardhat-test.ts +++ b/typescript/sdk/src/token/EvmERC20WarpModule.hardhat-test.ts @@ -132,13 +132,16 @@ describe('EvmERC20WarpHyperlaneModule', async () => { expect(tokenType).to.equal(TokenType.collateralVault); // Validate onchain token values - const collateralVault = HypERC20CollateralVaultDeposit__factory.connect( - deployedTokenRoute, - signer, + const collateralVaultContract = + HypERC20CollateralVaultDeposit__factory.connect( + deployedTokenRoute, + signer, + ); + await validateCoreValues(collateralVaultContract); + expect(await collateralVaultContract.vault()).to.equal(vault.address); + expect(await collateralVaultContract.wrappedToken()).to.equal( + token.address, ); - await validateCoreValues(collateralVault); - expect(await collateralVault.vault()).to.equal(vault.address); - expect(await collateralVault.wrappedToken()).to.equal(token.address); }); it('should create with a a synthetic config', async () => { @@ -167,12 +170,15 @@ describe('EvmERC20WarpHyperlaneModule', async () => { expect(tokenType).to.equal(TokenType.synthetic); // Validate onchain token values - const synthetic = HypERC20__factory.connect(deployedTokenRoute, signer); - await validateCoreValues(synthetic); - expect(await synthetic.name()).to.equal(TOKEN_NAME); - expect(await synthetic.symbol()).to.equal(TOKEN_NAME); - expect(await synthetic.decimals()).to.equal(TOKEN_DECIMALS); - expect(await synthetic.totalSupply()).to.equal(TOKEN_SUPPLY); + const syntheticContract = HypERC20__factory.connect( + deployedTokenRoute, + signer, + ); + await validateCoreValues(syntheticContract); + expect(await syntheticContract.name()).to.equal(TOKEN_NAME); + expect(await syntheticContract.symbol()).to.equal(TOKEN_NAME); + expect(await syntheticContract.decimals()).to.equal(TOKEN_DECIMALS); + expect(await syntheticContract.totalSupply()).to.equal(TOKEN_SUPPLY); }); it('should create with a a native config', async () => { @@ -196,7 +202,10 @@ describe('EvmERC20WarpHyperlaneModule', async () => { expect(tokenType).to.equal(TokenType.native); // Validate onchain token values - const native = HypNative__factory.connect(deployedTokenRoute, signer); - await validateCoreValues(native); + const nativeContract = HypNative__factory.connect( + deployedTokenRoute, + signer, + ); + await validateCoreValues(nativeContract); }); }); diff --git a/typescript/sdk/src/token/EvmERC20WarpModule.ts b/typescript/sdk/src/token/EvmERC20WarpModule.ts index a41101e79..20802e02f 100644 --- a/typescript/sdk/src/token/EvmERC20WarpModule.ts +++ b/typescript/sdk/src/token/EvmERC20WarpModule.ts @@ -34,7 +34,6 @@ export class EvmERC20WarpModule extends HyperlaneModule< >, ) { super(args); - this.reader = new EvmERC20WarpRouteReader(multiProvider, args.chain); } From 0df77ad97a796cd1d305b27f9c5e076292598632 Mon Sep 17 00:00:00 2001 From: Connor McEwen Date: Thu, 6 Jun 2024 17:50:10 -0400 Subject: [PATCH 28/73] build: 4.0.0-alpha.0 --- .changeset/pre.json | 17 +++++++++++++++++ solidity/CHANGELOG.md | 6 ++++++ solidity/package.json | 4 ++-- typescript/ccip-server/CHANGELOG.md | 2 ++ typescript/ccip-server/package.json | 2 +- typescript/cli/CHANGELOG.md | 13 +++++++++++++ typescript/cli/package.json | 6 +++--- typescript/helloworld/CHANGELOG.md | 9 +++++++++ typescript/helloworld/package.json | 6 +++--- typescript/infra/CHANGELOG.md | 10 ++++++++++ typescript/infra/package.json | 8 ++++---- typescript/sdk/CHANGELOG.md | 12 ++++++++++++ typescript/sdk/package.json | 6 +++--- typescript/utils/CHANGELOG.md | 2 ++ typescript/utils/package.json | 2 +- 15 files changed, 88 insertions(+), 17 deletions(-) create mode 100644 .changeset/pre.json diff --git a/.changeset/pre.json b/.changeset/pre.json new file mode 100644 index 000000000..6842eab03 --- /dev/null +++ b/.changeset/pre.json @@ -0,0 +1,17 @@ +{ + "mode": "pre", + "tag": "alpha", + "initialVersions": { + "@hyperlane-xyz/core": "4.0.0-alpha", + "@hyperlane-xyz/ccip-server": "4.0.0-alpha", + "@hyperlane-xyz/cli": "4.0.0-alpha", + "@hyperlane-xyz/helloworld": "4.0.0-alpha", + "@hyperlane-xyz/infra": "4.0.0-alpha", + "@hyperlane-xyz/sdk": "4.0.0-alpha", + "@hyperlane-xyz/utils": "4.0.0-alpha" + }, + "changesets": [ + "five-baboons-smoke", + "slimy-toys-argue" + ] +} diff --git a/solidity/CHANGELOG.md b/solidity/CHANGELOG.md index b910b1140..25b96c929 100644 --- a/solidity/CHANGELOG.md +++ b/solidity/CHANGELOG.md @@ -1,5 +1,11 @@ # @hyperlane-xyz/core +## 4.0.0-alpha.0 + +### Patch Changes + +- @hyperlane-xyz/utils@4.0.0-alpha.0 + ## 4.0.0 ### Major Changes diff --git a/solidity/package.json b/solidity/package.json index 9d2789f0d..14451bf5d 100644 --- a/solidity/package.json +++ b/solidity/package.json @@ -1,10 +1,10 @@ { "name": "@hyperlane-xyz/core", "description": "Core solidity contracts for Hyperlane", - "version": "4.0.0-alpha", + "version": "4.0.0-alpha.0", "dependencies": { "@eth-optimism/contracts": "^0.6.0", - "@hyperlane-xyz/utils": "4.0.0-alpha", + "@hyperlane-xyz/utils": "4.0.0-alpha.0", "@layerzerolabs/lz-evm-oapp-v2": "2.0.2", "@openzeppelin/contracts": "^4.9.3", "@openzeppelin/contracts-upgradeable": "^v4.9.3", diff --git a/typescript/ccip-server/CHANGELOG.md b/typescript/ccip-server/CHANGELOG.md index dff035e58..d9b7b80a1 100644 --- a/typescript/ccip-server/CHANGELOG.md +++ b/typescript/ccip-server/CHANGELOG.md @@ -1,5 +1,7 @@ # @hyperlane-xyz/ccip-server +## 4.0.0-alpha.0 + ## 4.0.0-alpha ## 3.13.0 diff --git a/typescript/ccip-server/package.json b/typescript/ccip-server/package.json index f6df925d7..181b27578 100644 --- a/typescript/ccip-server/package.json +++ b/typescript/ccip-server/package.json @@ -1,6 +1,6 @@ { "name": "@hyperlane-xyz/ccip-server", - "version": "4.0.0-alpha", + "version": "4.0.0-alpha.0", "description": "CCIP server", "typings": "dist/index.d.ts", "typedocMain": "src/index.ts", diff --git a/typescript/cli/CHANGELOG.md b/typescript/cli/CHANGELOG.md index ee64a3328..c7a67eca0 100644 --- a/typescript/cli/CHANGELOG.md +++ b/typescript/cli/CHANGELOG.md @@ -1,5 +1,18 @@ # @hyperlane-xyz/cli +## 4.0.0-alpha.0 + +### Minor Changes + +- 6db9fa9ad: Implement hyperlane warp deploy + +### Patch Changes + +- Updated dependencies [bdcbe1d16] +- Updated dependencies [6db9fa9ad] + - @hyperlane-xyz/sdk@4.0.0-alpha.0 + - @hyperlane-xyz/utils@4.0.0-alpha.0 + ## 4.0.0-alpha ### Major Changes diff --git a/typescript/cli/package.json b/typescript/cli/package.json index 33da93e66..6ea22877c 100644 --- a/typescript/cli/package.json +++ b/typescript/cli/package.json @@ -1,13 +1,13 @@ { "name": "@hyperlane-xyz/cli", - "version": "4.0.0-alpha", + "version": "4.0.0-alpha.0", "description": "A command-line utility for common Hyperlane operations", "dependencies": { "@aws-sdk/client-kms": "^3.577.0", "@aws-sdk/client-s3": "^3.577.0", "@hyperlane-xyz/registry": "1.3.0", - "@hyperlane-xyz/sdk": "4.0.0-alpha", - "@hyperlane-xyz/utils": "4.0.0-alpha", + "@hyperlane-xyz/sdk": "4.0.0-alpha.0", + "@hyperlane-xyz/utils": "4.0.0-alpha.0", "@inquirer/prompts": "^3.0.0", "asn1.js": "^5.4.1", "bignumber.js": "^9.1.1", diff --git a/typescript/helloworld/CHANGELOG.md b/typescript/helloworld/CHANGELOG.md index db79a5bfb..82b963cdd 100644 --- a/typescript/helloworld/CHANGELOG.md +++ b/typescript/helloworld/CHANGELOG.md @@ -1,5 +1,14 @@ # @hyperlane-xyz/helloworld +## 4.0.0-alpha.0 + +### Patch Changes + +- Updated dependencies [bdcbe1d16] +- Updated dependencies [6db9fa9ad] + - @hyperlane-xyz/sdk@4.0.0-alpha.0 + - @hyperlane-xyz/core@4.0.0-alpha.0 + ## 4.0.0-alpha ### Patch Changes diff --git a/typescript/helloworld/package.json b/typescript/helloworld/package.json index 8e78ca871..f1b071ce1 100644 --- a/typescript/helloworld/package.json +++ b/typescript/helloworld/package.json @@ -1,11 +1,11 @@ { "name": "@hyperlane-xyz/helloworld", "description": "A basic skeleton of an Hyperlane app", - "version": "4.0.0-alpha", + "version": "4.0.0-alpha.0", "dependencies": { - "@hyperlane-xyz/core": "4.0.0-alpha", + "@hyperlane-xyz/core": "4.0.0-alpha.0", "@hyperlane-xyz/registry": "1.3.0", - "@hyperlane-xyz/sdk": "4.0.0-alpha", + "@hyperlane-xyz/sdk": "4.0.0-alpha.0", "@openzeppelin/contracts-upgradeable": "^4.9.3", "ethers": "^5.7.2" }, diff --git a/typescript/infra/CHANGELOG.md b/typescript/infra/CHANGELOG.md index a960a7da3..0c783da25 100644 --- a/typescript/infra/CHANGELOG.md +++ b/typescript/infra/CHANGELOG.md @@ -1,5 +1,15 @@ # @hyperlane-xyz/infra +## 4.0.0-alpha.0 + +### Patch Changes + +- Updated dependencies [bdcbe1d16] +- Updated dependencies [6db9fa9ad] + - @hyperlane-xyz/sdk@4.0.0-alpha.0 + - @hyperlane-xyz/helloworld@4.0.0-alpha.0 + - @hyperlane-xyz/utils@4.0.0-alpha.0 + ## 4.0.0-alpha ### Minor Changes diff --git a/typescript/infra/package.json b/typescript/infra/package.json index dbced2f38..ef420d668 100644 --- a/typescript/infra/package.json +++ b/typescript/infra/package.json @@ -1,7 +1,7 @@ { "name": "@hyperlane-xyz/infra", "description": "Infrastructure utilities for the Hyperlane Network", - "version": "4.0.0-alpha", + "version": "4.0.0-alpha.0", "dependencies": { "@arbitrum/sdk": "^3.0.0", "@aws-sdk/client-iam": "^3.74.0", @@ -12,10 +12,10 @@ "@ethersproject/experimental": "^5.7.0", "@ethersproject/hardware-wallets": "^5.7.0", "@ethersproject/providers": "^5.7.2", - "@hyperlane-xyz/helloworld": "4.0.0-alpha", + "@hyperlane-xyz/helloworld": "4.0.0-alpha.0", "@hyperlane-xyz/registry": "1.3.0", - "@hyperlane-xyz/sdk": "4.0.0-alpha", - "@hyperlane-xyz/utils": "4.0.0-alpha", + "@hyperlane-xyz/sdk": "4.0.0-alpha.0", + "@hyperlane-xyz/utils": "4.0.0-alpha.0", "@nomiclabs/hardhat-etherscan": "^3.0.3", "@solana/web3.js": "^1.78.0", "asn1.js": "5.4.1", diff --git a/typescript/sdk/CHANGELOG.md b/typescript/sdk/CHANGELOG.md index 293aff181..efcec9c94 100644 --- a/typescript/sdk/CHANGELOG.md +++ b/typescript/sdk/CHANGELOG.md @@ -1,5 +1,17 @@ # @hyperlane-xyz/sdk +## 4.0.0-alpha.0 + +### Minor Changes + +- bdcbe1d16: Add EvmWarpModule with create() +- 6db9fa9ad: Implement hyperlane warp deploy + +### Patch Changes + +- @hyperlane-xyz/core@4.0.0-alpha.0 +- @hyperlane-xyz/utils@4.0.0-alpha.0 + ## 4.0.0-alpha ### Major Changes diff --git a/typescript/sdk/package.json b/typescript/sdk/package.json index 094d8764a..2eed8cbf3 100644 --- a/typescript/sdk/package.json +++ b/typescript/sdk/package.json @@ -1,13 +1,13 @@ { "name": "@hyperlane-xyz/sdk", "description": "The official SDK for the Hyperlane Network", - "version": "4.0.0-alpha", + "version": "4.0.0-alpha.0", "dependencies": { "@aws-sdk/client-s3": "^3.74.0", "@cosmjs/cosmwasm-stargate": "^0.31.3", "@cosmjs/stargate": "^0.31.3", - "@hyperlane-xyz/core": "4.0.0-alpha", - "@hyperlane-xyz/utils": "4.0.0-alpha", + "@hyperlane-xyz/core": "4.0.0-alpha.0", + "@hyperlane-xyz/utils": "4.0.0-alpha.0", "@safe-global/api-kit": "1.3.0", "@safe-global/protocol-kit": "1.3.0", "@solana/spl-token": "^0.3.8", diff --git a/typescript/utils/CHANGELOG.md b/typescript/utils/CHANGELOG.md index 4a7c2aaac..356c4d74c 100644 --- a/typescript/utils/CHANGELOG.md +++ b/typescript/utils/CHANGELOG.md @@ -1,5 +1,7 @@ # @hyperlane-xyz/utils +## 4.0.0-alpha.0 + ## 4.0.0-alpha ### Major Changes diff --git a/typescript/utils/package.json b/typescript/utils/package.json index 5147cbd26..579b5e306 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": "4.0.0-alpha", + "version": "4.0.0-alpha.0", "dependencies": { "@cosmjs/encoding": "^0.31.3", "@solana/web3.js": "^1.78.0", From 20571c2a4c270b39f9396de59f7b93ec02c7c7b5 Mon Sep 17 00:00:00 2001 From: Connor McEwen Date: Thu, 6 Jun 2024 17:50:51 -0400 Subject: [PATCH 29/73] fix: version string --- typescript/cli/src/version.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typescript/cli/src/version.ts b/typescript/cli/src/version.ts index c94009698..3a8af1c6d 100644 --- a/typescript/cli/src/version.ts +++ b/typescript/cli/src/version.ts @@ -1 +1 @@ -export const VERSION = '4.0.0-alpha'; +export const VERSION = '4.0.0-alpha.0'; From 4b29717f56368b79ebf9ac9b854eb0d77c708312 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Noah=20Bayindirli=20=F0=9F=A5=82?= Date: Fri, 7 Jun 2024 18:22:49 -0400 Subject: [PATCH 30/73] fix(cli): no prompt on default hooks in hyperlane core configure (#3912) ### Description * removes prompt on default hooks in `hyperlane core configure` * required hook now defaults to protocol and default hook defaults to merkle ### Drive-by changes - reshuffling `hyperlane warp configure` to accommodate the change as well (and maintain symmetry) ### Related issues - fixes https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/3895 ### Backward compatibility - yes ### Testing - manual - ci-test --- typescript/cli/src/commands/core.ts | 74 ++----- typescript/cli/src/commands/warp.ts | 24 +- typescript/cli/src/config/core.ts | 71 ++++++ typescript/cli/src/config/hooks.ts | 333 ++++++++++++++++------------ typescript/cli/src/config/ism.ts | 297 ++++++++++++++++--------- typescript/cli/src/config/utils.ts | 18 ++ typescript/cli/src/config/warp.ts | 33 +-- typescript/cli/src/utils/chains.ts | 2 +- typescript/cli/src/utils/files.ts | 8 + yarn.lock | 28 +-- 10 files changed, 546 insertions(+), 342 deletions(-) create mode 100644 typescript/cli/src/config/core.ts create mode 100644 typescript/cli/src/config/utils.ts diff --git a/typescript/cli/src/commands/core.ts b/typescript/cli/src/commands/core.ts index 56e2c3949..91b7f43b1 100644 --- a/typescript/cli/src/commands/core.ts +++ b/typescript/cli/src/commands/core.ts @@ -1,24 +1,21 @@ +import { stringify as yamlStringify } from 'yaml'; import { CommandModule } from 'yargs'; -import { CoreConfigSchema, EvmCoreReader, IsmConfig } from '@hyperlane-xyz/sdk'; +import { EvmCoreReader } from '@hyperlane-xyz/sdk'; -import { createHookConfig } from '../config/hooks.js'; -import { createIsmConfig, createTrustedRelayerConfig } from '../config/ism.js'; +import { createCoreDeployConfig } from '../config/core.js'; import { CommandModuleWithContext, CommandModuleWithWriteContext, } from '../context/types.js'; import { runCoreDeploy } from '../deploy/core.js'; import { evaluateIfDryRunFailure } from '../deploy/dry-run.js'; +import { log, logGray, logGreen } from '../logger.js'; import { - log, - logBlue, - logBoldUnderlinedRed, - logGray, - logRed, -} from '../logger.js'; -import { detectAndConfirmOrPrompt } from '../utils/chains.js'; -import { readYamlOrJson, writeYamlOrJson } from '../utils/files.js'; + indentYamlOrJson, + readYamlOrJson, + writeYamlOrJson, +} from '../utils/files.js'; import { chainCommandOption, @@ -86,13 +83,13 @@ export const deploy: CommandModuleWithWriteContext<{ }; export const configure: CommandModuleWithContext<{ - ismAdvanced: boolean; + advanced: boolean; config: string; }> = { command: 'configure', describe: 'Create a core configuration, including ISMs and hooks.', builder: { - ismAdvanced: { + advanced: { type: 'boolean', describe: 'Create an advanced ISM & hook configuration', default: false, @@ -103,50 +100,15 @@ export const configure: CommandModuleWithContext<{ 'The path to output a Core Config JSON or YAML file.', ), }, - handler: async ({ context, ismAdvanced, config: configFilePath }) => { + handler: async ({ context, advanced, config: configFilePath }) => { logGray('Hyperlane Core Configure'); logGray('------------------------'); - const owner = await detectAndConfirmOrPrompt( - async () => context.signer?.getAddress(), - 'Enter the desired', - 'owner address', - 'signer', - ); - - // Create default Ism config (advanced or trusted) - let defaultIsm: IsmConfig; - if (ismAdvanced) { - 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.', - ); - defaultIsm = await createIsmConfig(context); - } else { - defaultIsm = await createTrustedRelayerConfig(context); - } - - // Create default and required Hook config - const defaultHook = await createHookConfig( - context, - 'Select default hook type', - ); - const requiredHook = await createHookConfig( + await createCoreDeployConfig({ context, - 'Select required hook type', - ); - - // Validate - const coreConfig = { - owner, - defaultIsm, - defaultHook, - requiredHook, - }; - CoreConfigSchema.parse(coreConfig); - - writeYamlOrJson(configFilePath, coreConfig); + configFilePath, + advanced, + }); process.exit(0); }, @@ -182,7 +144,11 @@ export const read: CommandModuleWithContext<{ const evmCoreReader = new EvmCoreReader(context.multiProvider, chain); const coreConfig = await evmCoreReader.deriveCoreConfig(mailbox); - writeYamlOrJson(configFilePath, coreConfig); + writeYamlOrJson(configFilePath, coreConfig, 'yaml'); + logGreen( + `✅ Warp route config written successfully to ${configFilePath}:\n`, + ); + log(indentYamlOrJson(yamlStringify(coreConfig, null, 2), 4)); process.exit(0); }, diff --git a/typescript/cli/src/commands/warp.ts b/typescript/cli/src/commands/warp.ts index 309485def..536ef0b1e 100644 --- a/typescript/cli/src/commands/warp.ts +++ b/typescript/cli/src/commands/warp.ts @@ -1,3 +1,4 @@ +import { stringify as yamlStringify } from 'yaml'; import { CommandModule } from 'yargs'; import { EvmERC20WarpRouteReader } from '@hyperlane-xyz/sdk'; @@ -11,7 +12,7 @@ import { evaluateIfDryRunFailure } from '../deploy/dry-run.js'; import { runWarpRouteDeploy } from '../deploy/warp.js'; import { log, logGray, logGreen } from '../logger.js'; import { sendTestTransfer } from '../send/transfer.js'; -import { writeFileAtPath } from '../utils/files.js'; +import { indentYamlOrJson, writeYamlOrJson } from '../utils/files.js'; import { addressCommandOption, @@ -72,24 +73,27 @@ export const deploy: CommandModuleWithWriteContext<{ }; export const configure: CommandModuleWithContext<{ - ismAdvanced: boolean; + advanced: boolean; out: string; }> = { command: 'configure', describe: 'Create a warp route configuration.', builder: { - ismAdvanced: { + advanced: { type: 'boolean', - describe: 'Create an advanced ISM & hook configuration', + describe: 'Create an advanced ISM', default: false, }, out: outputFileCommandOption('./configs/warp-route-deployment.yaml'), }, - handler: async ({ context, ismAdvanced, out }) => { + handler: async ({ context, advanced, out }) => { + logGray('Hyperlane Warp Configure'); + logGray('------------------------'); + await createWarpRouteDeployConfig({ context, outPath: out, - shouldUseDefault: !ismAdvanced, + advanced, }); process.exit(0); }, @@ -126,12 +130,12 @@ export const read: CommandModuleWithContext<{ address, ); if (out) { - writeFileAtPath(out, JSON.stringify(warpRouteConfig, null, 4) + '\n'); - logGreen(`✅ Warp route config written successfully to ${out}.`); + writeYamlOrJson(out, warpRouteConfig, 'yaml'); + logGreen(`✅ Warp route config written successfully to ${out}:\n`); } else { - logGreen(`✅ Warp route config read successfully:`); - log(JSON.stringify(warpRouteConfig, null, 4)); + logGreen(`✅ Warp route config read successfully:\n`); } + log(indentYamlOrJson(yamlStringify(warpRouteConfig, null, 2), 4)); process.exit(0); }, }; diff --git a/typescript/cli/src/config/core.ts b/typescript/cli/src/config/core.ts new file mode 100644 index 000000000..776d35c96 --- /dev/null +++ b/typescript/cli/src/config/core.ts @@ -0,0 +1,71 @@ +import { stringify as yamlStringify } from 'yaml'; + +import { CoreConfigSchema, HookConfig, IsmConfig } from '@hyperlane-xyz/sdk'; + +import { CommandContext } from '../context/types.js'; +import { errorRed, log, logBlue, logGreen } from '../logger.js'; +import { detectAndConfirmOrPrompt } from '../utils/chains.js'; +import { indentYamlOrJson, writeYamlOrJson } from '../utils/files.js'; + +import { + createHookConfig, + createMerkleTreeConfig, + createProtocolFeeConfig, +} from './hooks.js'; +import { createAdvancedIsmConfig, createTrustedRelayerConfig } from './ism.js'; + +export async function createCoreDeployConfig({ + context, + configFilePath, + advanced = false, +}: { + context: CommandContext; + configFilePath: string; + advanced: boolean; +}) { + logBlue('Creating a new core deployment config...'); + + const owner = await detectAndConfirmOrPrompt( + async () => context.signer?.getAddress(), + 'Enter the desired', + 'owner address', + 'signer', + ); + + const defaultIsm: IsmConfig = advanced + ? await createAdvancedIsmConfig(context) + : await createTrustedRelayerConfig(context, advanced); + + let defaultHook: HookConfig, requiredHook: HookConfig; + if (advanced) { + defaultHook = await createHookConfig({ + context, + selectMessage: 'Select default hook type', + advanced, + }); + requiredHook = await createHookConfig({ + context, + selectMessage: 'Select required hook type', + advanced, + }); + } else { + defaultHook = await createMerkleTreeConfig(); + requiredHook = await createProtocolFeeConfig(context, advanced); + } + + try { + const coreConfig = CoreConfigSchema.parse({ + owner, + defaultIsm, + defaultHook, + requiredHook, + }); + logBlue(`Core config is valid, writing to file ${configFilePath}:\n`); + log(indentYamlOrJson(yamlStringify(coreConfig, null, 2), 4)); + writeYamlOrJson(configFilePath, coreConfig, 'yaml'); + logGreen('✅ Successfully created new core deployment config.'); + } catch (e) { + errorRed(`Core deployment config is invalid.`); + throw e; + } +} diff --git a/typescript/cli/src/config/hooks.ts b/typescript/cli/src/config/hooks.ts index 48c913a46..f5cb37451 100644 --- a/typescript/cli/src/config/hooks.ts +++ b/typescript/cli/src/config/hooks.ts @@ -19,9 +19,14 @@ import { import { CommandContext } from '../context/types.js'; import { errorRed, logBlue, logGreen, logRed } from '../logger.js'; -import { runMultiChainSelectionStep } from '../utils/chains.js'; +import { + detectAndConfirmOrPrompt, + runMultiChainSelectionStep, +} from '../utils/chains.js'; import { readYamlOrJson } from '../utils/files.js'; +import { callWithConfigCreationLogs } from './utils.js'; + // TODO: deprecate in favor of CoreConfigSchema const HooksConfigSchema = z.object({ default: HookConfigSchema, @@ -31,6 +36,9 @@ export type HooksConfig = z.infer; const HooksConfigMapSchema = z.record(HooksConfigSchema); export type HooksConfigMap = z.infer; +const MAX_PROTOCOL_FEE_DEFAULT: string = toWei('0.1'); +const PROTOCOL_FEE_DEFAULT: string = toWei('0'); + export function presetHookConfigs(owner: Address): HooksConfig { return { required: { @@ -61,14 +69,24 @@ export function readHooksConfigMap(filePath: string) { return hooks; } -export async function createHookConfig( - context: CommandContext, +export async function createHookConfig({ + context, selectMessage = 'Select hook type', -): Promise { - let lastConfig: HookConfig; + advanced = false, +}: { + context: CommandContext; + selectMessage?: string; + advanced?: boolean; +}): Promise { const hookType = await select({ message: selectMessage, choices: [ + { + 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.MERKLE_TREE, name: HookType.MERKLE_TREE, @@ -80,155 +98,188 @@ export async function createHookConfig( name: HookType.PROTOCOL_FEE, description: 'Charge fees for each message dispatch from this 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', - }, ], pageSize: 10, }); - if (hookType === HookType.MERKLE_TREE) { - lastConfig = { type: HookType.MERKLE_TREE }; - } else if (hookType === HookType.PROTOCOL_FEE) { - lastConfig = await createProtocolFeeConfig(); - } else if (hookType === HookType.AGGREGATION) { - lastConfig = await createAggregationConfig(context); - } else { - throw new Error(`Invalid hook type: ${hookType}`); + + switch (hookType) { + case HookType.AGGREGATION: + return createAggregationConfig(context, advanced); + case HookType.MERKLE_TREE: + return createMerkleTreeConfig(); + case HookType.PROTOCOL_FEE: + return createProtocolFeeConfig(context, advanced); + default: + throw new Error(`Invalid hook type: ${hookType}`); } - return lastConfig; } -export async function createProtocolFeeConfig(): Promise { - const owner = await input({ - message: 'Enter owner address for protocol fee hook', - }); - 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 for protocol fee hook', - }); - } - const beneficiaryAddress = normalizeAddressEvm(beneficiary); - // TODO: input in gwei, wei, etc - const maxProtocolFee = toWei( - await input({ - message: `Enter max protocol fee for protocol fee hook`, - }), - ); - const protocolFee = toWei( - await input({ - message: `Enter protocol fee in for protocol fee hook`, - }), - ); - if (BigNumberJs(protocolFee).gt(maxProtocolFee)) { - errorRed('Protocol fee cannot be greater than max protocol fee'); - throw new Error('Invalid protocol fee'); - } +export const createMerkleTreeConfig = callWithConfigCreationLogs( + async (): Promise => { + return { type: HookType.MERKLE_TREE }; + }, + HookType.MERKLE_TREE, +); - return { - type: HookType.PROTOCOL_FEE, - maxProtocolFee: maxProtocolFee.toString(), - protocolFee: protocolFee.toString(), - beneficiary: beneficiaryAddress, - owner: ownerAddress, - }; -} +export const createProtocolFeeConfig = callWithConfigCreationLogs( + async ( + context: CommandContext, + advanced: boolean = false, + ): Promise => { + const unnormalizedOwner = + !advanced && context.signer + ? await context.signer.getAddress() + : await detectAndConfirmOrPrompt( + async () => context.signer?.getAddress(), + 'For protocol fee hook, enter', + 'owner address', + 'signer', + ); + const owner = normalizeAddressEvm(unnormalizedOwner); + let beneficiary = owner; + + const isBeneficiarySameAsOwner = advanced + ? await confirm({ + message: `Use this same address (${owner}) for the beneficiary?`, + }) + : true; + + if (!isBeneficiarySameAsOwner) { + const unnormalizedBeneficiary = await input({ + message: 'Enter beneficiary address for protocol fee hook:', + }); + beneficiary = normalizeAddressEvm(unnormalizedBeneficiary); + } + // TODO: input in gwei, wei, etc + const maxProtocolFee = advanced + ? toWei( + await input({ + message: `Enter max protocol fee for protocol fee hook (in wei):`, + }), + ) + : MAX_PROTOCOL_FEE_DEFAULT; + const protocolFee = advanced + ? toWei( + await input({ + message: `Enter protocol fee for protocol fee hook (in wei):`, + }), + ) + : PROTOCOL_FEE_DEFAULT; + if (BigNumberJs(protocolFee).gt(maxProtocolFee)) { + errorRed( + `Protocol fee (${protocolFee}) cannot be greater than max protocol fee (${maxProtocolFee}).`, + ); + throw new Error('Invalid protocol fee.'); + } + return { + type: HookType.PROTOCOL_FEE, + maxProtocolFee, + protocolFee, + beneficiary, + owner, + }; + }, + HookType.PROTOCOL_FEE, +); // TODO: make this usable -export async function createIGPConfig( - remotes: ChainName[], -): Promise { - const owner = await input({ - message: 'Enter owner address for IGP hook', - }); - 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 for IGP hook', +export const createIGPConfig = callWithConfigCreationLogs( + async (remotes: ChainName[]): Promise => { + const unnormalizedOwner = await input({ + message: 'Enter owner address for IGP hook', }); - oracleKey = await input({ - message: 'Enter gasOracleKey address for IGP hook', + const owner = normalizeAddressEvm(unnormalizedOwner); + let beneficiary = owner; + let oracleKey = owner; + + const beneficiarySameAsOwner = await confirm({ + message: 'Use this same address for the beneficiary and gasOracleKey?', }); - } - const beneficiaryAddress = normalizeAddressEvm(beneficiary); - const oracleKeyAddress = normalizeAddressEvm(oracleKey); - const overheads: ChainMap = {}; - for (const chain of remotes) { - const overhead = parseInt( + + if (!beneficiarySameAsOwner) { + const unnormalizedBeneficiary = await input({ + message: 'Enter beneficiary address for IGP hook', + }); + beneficiary = normalizeAddressEvm(unnormalizedBeneficiary); + const unnormalizedOracleKey = await input({ + message: 'Enter gasOracleKey address for IGP hook', + }); + oracleKey = normalizeAddressEvm(unnormalizedOracleKey); + } + const overheads: ChainMap = {}; + for (const chain of remotes) { + const overhead = parseInt( + await input({ + message: `Enter overhead for ${chain} (eg 75000) for IGP hook`, + }), + ); + overheads[chain] = overhead; + } + return { + type: HookType.INTERCHAIN_GAS_PAYMASTER, + beneficiary, + owner, + oracleKey, + overhead: overheads, + oracleConfig: {}, + }; + }, + HookType.INTERCHAIN_GAS_PAYMASTER, +); + +export const createAggregationConfig = callWithConfigCreationLogs( + async ( + context: CommandContext, + advanced: boolean = false, + ): Promise => { + const hooksNum = parseInt( await input({ - message: `Enter overhead for ${chain} (eg 75000) for IGP hook`, + message: 'Enter the number of hooks to aggregate (number)', }), + 10, ); - overheads[chain] = overhead; - } - return { - type: HookType.INTERCHAIN_GAS_PAYMASTER, - beneficiary: beneficiaryAddress, - owner: ownerAddress, - oracleKey: oracleKeyAddress, - overhead: overheads, - oracleConfig: {}, - }; -} + const hooks: Array = []; + for (let i = 0; i < hooksNum; i++) { + logBlue(`Creating hook ${i + 1} of ${hooksNum} ...`); + hooks.push( + await createHookConfig({ + context, + advanced, + }), + ); + } + return { + type: HookType.AGGREGATION, + hooks, + }; + }, + HookType.AGGREGATION, +); -export async function createAggregationConfig( - context: CommandContext, -): 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(context)); - } - return { - type: HookType.AGGREGATION, - hooks, - }; -} - -export async function createRoutingConfig( - context: CommandContext, -): Promise { - const owner = await input({ - message: 'Enter owner address for routing ISM', - }); - const ownerAddress = owner; - const chains = await runMultiChainSelectionStep(context.chainMetadata); - - const domainsMap: ChainMap = {}; - for (const chain of chains) { - await confirm({ - message: `You are about to configure hook for remote chain ${chain}. Continue?`, +export const createRoutingConfig = callWithConfigCreationLogs( + async ( + context: CommandContext, + advanced: boolean = false, + ): Promise => { + const owner = await input({ + message: 'Enter owner address for routing ISM', }); - const config = await createHookConfig(context); - domainsMap[chain] = config; - } - return { - type: HookType.ROUTING, - owner: ownerAddress, - domains: domainsMap, - }; -} + const ownerAddress = owner; + const chains = await runMultiChainSelectionStep(context.chainMetadata); + + const domainsMap: ChainMap = {}; + for (const chain of chains) { + await confirm({ + message: `You are about to configure hook for remote chain ${chain}. Continue?`, + }); + const config = await createHookConfig({ context, advanced }); + domainsMap[chain] = config; + } + return { + type: HookType.ROUTING, + owner: ownerAddress, + domains: domainsMap, + }; + }, + HookType.ROUTING, +); diff --git a/typescript/cli/src/config/ism.ts b/typescript/cli/src/config/ism.ts index 7ec1b6699..64ae7b099 100644 --- a/typescript/cli/src/config/ism.ts +++ b/typescript/cli/src/config/ism.ts @@ -1,4 +1,4 @@ -import { confirm, input, select } from '@inquirer/prompts'; +import { input, select } from '@inquirer/prompts'; import { z } from 'zod'; import { @@ -12,12 +12,21 @@ import { } from '@hyperlane-xyz/sdk'; import { CommandContext } from '../context/types.js'; +import { + errorRed, + log, + logBlue, + logBoldUnderlinedRed, + logRed, +} from '../logger.js'; import { detectAndConfirmOrPrompt, runMultiChainSelectionStep, } from '../utils/chains.js'; import { readYamlOrJson } from '../utils/files.js'; +import { callWithConfigCreationLogs } from './utils.js'; + const IsmConfigMapSchema = z.record(IsmConfigSchema).refine( (ismConfigMap) => { // check if any key in IsmConfigMap is found in its own RoutingIsmConfigSchema.domains @@ -59,23 +68,29 @@ export function readIsmConfig(filePath: string) { } const ISM_TYPE_DESCRIPTIONS: Record = { - [IsmType.MESSAGE_ID_MULTISIG]: 'Validators need to sign just this messageId', + [IsmType.AGGREGATION]: + 'You can aggregate multiple ISMs into one ISM via AggregationISM', + [IsmType.FALLBACK_ROUTING]: + "You can specify ISM type for specific chains you like and fallback to mailbox's default ISM for other chains via DefaultFallbackRoutingISM", [IsmType.MERKLE_ROOT_MULTISIG]: 'Validators need to sign the root of the merkle tree of all messages from origin chain', + [IsmType.MESSAGE_ID_MULTISIG]: 'Validators need to sign just this messageId', [IsmType.ROUTING]: 'Each origin chain can be verified by the specified ISM type via RoutingISM', - [IsmType.FALLBACK_ROUTING]: - "You can specify ISM type for specific chains you like and fallback to mailbox's default ISM for other chains via DefaultFallbackRoutingISM", - [IsmType.AGGREGATION]: - 'You can aggregate multiple ISMs into one ISM via AggregationISM', - [IsmType.TRUSTED_RELAYER]: 'Deliver messages from an authorized address', [IsmType.TEST_ISM]: 'ISM where you can deliver messages without any validation (WARNING: only for testing, do not use in production)', + [IsmType.TRUSTED_RELAYER]: 'Deliver messages from an authorized address', }; -export async function createIsmConfig( +export async function createAdvancedIsmConfig( context: CommandContext, ): Promise { + 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 moduleType = await select({ message: 'Select ISM type', choices: Object.entries(ISM_TYPE_DESCRIPTIONS).map( @@ -87,116 +102,180 @@ export async function createIsmConfig( pageSize: 10, }); - if ( - moduleType === IsmType.MESSAGE_ID_MULTISIG || - moduleType === IsmType.MERKLE_ROOT_MULTISIG - ) { - return createMultisigConfig(moduleType); - } else if ( - moduleType === IsmType.ROUTING || - moduleType === IsmType.FALLBACK_ROUTING - ) { - return createRoutingConfig(context, moduleType); - } else if (moduleType === IsmType.AGGREGATION) { - return createAggregationConfig(context); - } else if (moduleType === IsmType.TEST_ISM) { - return { type: IsmType.TEST_ISM }; - } else if (moduleType === IsmType.TRUSTED_RELAYER) { - return createTrustedRelayerConfig(context); + switch (moduleType) { + case IsmType.AGGREGATION: + return createAggregationConfig(context); + case IsmType.FALLBACK_ROUTING: + return createFallbackRoutingConfig(context); + case IsmType.MERKLE_ROOT_MULTISIG: + return createMerkleRootMultisigConfig(context); + case IsmType.MESSAGE_ID_MULTISIG: + return createMessageIdMultisigConfig(context); + case IsmType.ROUTING: + return createRoutingConfig(context); + case IsmType.TEST_ISM: + return { type: IsmType.TEST_ISM }; + case IsmType.TRUSTED_RELAYER: + return createTrustedRelayerConfig(context, true); + default: + throw new Error(`Invalid ISM type: ${moduleType}.`); } - - throw new Error(`Invalid ISM type: ${moduleType}`); } -export async function createMultisigConfig( - type: IsmType.MERKLE_ROOT_MULTISIG | IsmType.MESSAGE_ID_MULTISIG, -): Promise { - const thresholdInput = await input({ - message: 'Enter threshold of validators (number) for multisig ISM', - }); - const threshold = parseInt(thresholdInput, 10); +export const createMerkleRootMultisigConfig = callWithConfigCreationLogs( + async (): Promise => { + const validatorsInput = await input({ + message: + 'Enter validator addresses (comma separated list) for merkle root multisig ISM:', + }); + const validators = validatorsInput.split(',').map((v) => v.trim()); + const thresholdInput = await input({ + message: + 'Enter threshold of validators (number) for merkle root multisig ISM:', + }); + const threshold = parseInt(thresholdInput, 10); + if (threshold > validators.length) { + errorRed( + `Merkle root multisig signer threshold (${threshold}) cannot be greater than total number of validators (${validators.length}).`, + ); + throw new Error('Invalid protocol fee.'); + } + return { + type: IsmType.MERKLE_ROOT_MULTISIG, + threshold, + validators, + }; + }, + IsmType.MERKLE_ROOT_MULTISIG, +); - const validatorsInput = await input({ - message: - 'Enter validator addresses (comma separated list) for multisig ISM', - }); - const validators = validatorsInput.split(',').map((v) => v.trim()); - return { - type, - threshold, - validators, - }; -} +export const createMessageIdMultisigConfig = callWithConfigCreationLogs( + async (): Promise => { + const thresholdInput = await input({ + message: + 'Enter threshold of validators (number) for message ID multisig ISM', + }); + const threshold = parseInt(thresholdInput, 10); -export async function createTrustedRelayerConfig( - context: CommandContext, -): Promise { - const relayer = await detectAndConfirmOrPrompt( - async () => context.signer?.getAddress(), - 'For trusted relayer ISM, enter', - 'relayer address', - 'signer', - ); - return { - type: IsmType.TRUSTED_RELAYER, - relayer, - }; -} + const validatorsInput = await input({ + message: + 'Enter validator addresses (comma separated list) for message ID multisig ISM', + }); + const validators = validatorsInput.split(',').map((v) => v.trim()); + return { + type: IsmType.MESSAGE_ID_MULTISIG, + threshold, + validators, + }; + }, + IsmType.MESSAGE_ID_MULTISIG, +); -export async function createAggregationConfig( - context: CommandContext, -): Promise { - const isms = parseInt( - await input({ - message: 'Enter the number of ISMs to aggregate (number)', - }), - 10, - ); +export const createTrustedRelayerConfig = callWithConfigCreationLogs( + async ( + context: CommandContext, + advanced: boolean = false, + ): Promise => { + const relayer = + !advanced && context.signer + ? await context.signer.getAddress() + : await detectAndConfirmOrPrompt( + async () => context.signer?.getAddress(), + 'For trusted relayer ISM, enter', + 'relayer address', + 'signer', + ); + return { + type: IsmType.TRUSTED_RELAYER, + relayer, + }; + }, + IsmType.TRUSTED_RELAYER, +); - const threshold = parseInt( - await input({ - message: 'Enter the threshold of ISMs for verification (number)', - }), - 10, - ); +export const createAggregationConfig = callWithConfigCreationLogs( + async (context: CommandContext): Promise => { + const isms = parseInt( + await input({ + message: 'Enter the number of ISMs to aggregate (number)', + }), + 10, + ); - const modules: Array = []; - for (let i = 0; i < isms; i++) { - modules.push(await createIsmConfig(context)); - } - return { - type: IsmType.AGGREGATION, - modules, - threshold, - }; -} + const threshold = parseInt( + await input({ + message: 'Enter the threshold of ISMs for verification (number)', + }), + 10, + ); -export async function createRoutingConfig( - context: CommandContext, - type: IsmType.ROUTING | IsmType.FALLBACK_ROUTING, -): Promise { - const owner = await input({ - message: 'Enter owner address for routing ISM', - }); - const ownerAddress = owner; + const modules: Array = []; + for (let i = 0; i < isms; i++) { + modules.push(await createAdvancedIsmConfig(context)); + } + return { + type: IsmType.AGGREGATION, + modules, + threshold, + }; + }, + IsmType.AGGREGATION, +); - const chains = await runMultiChainSelectionStep( - context.chainMetadata, - 'Select chains to configure ISM for', - true, - ); +export const createRoutingConfig = callWithConfigCreationLogs( + async (context: CommandContext): Promise => { + const owner = await input({ + message: 'Enter owner address for routing ISM', + }); + const ownerAddress = owner; + const requireMultiple = true; + const chains = await runMultiChainSelectionStep( + context.chainMetadata, + 'Select chains to configure routing ISM for', + requireMultiple, + ); + + const domainsMap: ChainMap = {}; + for (const chain of chains) { + log(`You are about to configure routing ISM from source chain ${chain}.`); + const config = await createAdvancedIsmConfig(context); + domainsMap[chain] = config; + } + return { + type: IsmType.ROUTING, + owner: ownerAddress, + domains: domainsMap, + }; + }, + IsmType.ROUTING, +); - const domainsMap: ChainMap = {}; - for (const chain of chains) { - await confirm({ - message: `You are about to configure ISM from source chain ${chain}. Continue?`, +export const createFallbackRoutingConfig = callWithConfigCreationLogs( + async (context: CommandContext): Promise => { + const owner = await input({ + message: 'Enter owner address for fallback routing ISM', }); - const config = await createIsmConfig(context); - domainsMap[chain] = config; - } - return { - type, - owner: ownerAddress, - domains: domainsMap, - }; -} + const ownerAddress = owner; + + const chains = await runMultiChainSelectionStep( + context.chainMetadata, + 'Select chains to configure fallback routing ISM for', + true, + ); + + const domainsMap: ChainMap = {}; + for (const chain of chains) { + log( + `You are about to configure fallback routing ISM from source chain ${chain}.`, + ); + const config = await createAdvancedIsmConfig(context); + domainsMap[chain] = config; + } + return { + type: IsmType.FALLBACK_ROUTING, + owner: ownerAddress, + domains: domainsMap, + }; + }, + IsmType.FALLBACK_ROUTING, +); diff --git a/typescript/cli/src/config/utils.ts b/typescript/cli/src/config/utils.ts new file mode 100644 index 000000000..abd1c6f82 --- /dev/null +++ b/typescript/cli/src/config/utils.ts @@ -0,0 +1,18 @@ +import { HookConfig, HookType, IsmConfig, IsmType } from '@hyperlane-xyz/sdk'; + +import { logGray } from '../logger.js'; + +export function callWithConfigCreationLogs( + fn: (...args: any[]) => Promise, + type: IsmType | HookType, +) { + return async (...args: any[]): Promise => { + logGray(`Creating ${type}...`); + try { + const result = await fn(...args); + return result; + } finally { + logGray(`Created ${type}!`); + } + }; +} diff --git a/typescript/cli/src/config/warp.ts b/typescript/cli/src/config/warp.ts index 3f18c0ed5..c3de78eff 100644 --- a/typescript/cli/src/config/warp.ts +++ b/typescript/cli/src/config/warp.ts @@ -1,4 +1,5 @@ import { input, select } from '@inquirer/prompts'; +import { stringify as yamlStringify } from 'yaml'; import { ChainMap, @@ -14,15 +15,19 @@ import { import { assert, objMap, promiseObjAll } from '@hyperlane-xyz/utils'; import { CommandContext } from '../context/types.js'; -import { errorRed, logBlue, logGreen } from '../logger.js'; +import { errorRed, log, logBlue, logGreen } from '../logger.js'; import { detectAndConfirmOrPrompt, runMultiChainSelectionStep, } from '../utils/chains.js'; -import { readYamlOrJson, writeYamlOrJson } from '../utils/files.js'; +import { + indentYamlOrJson, + readYamlOrJson, + writeYamlOrJson, +} from '../utils/files.js'; import { - createIsmConfig, + createAdvancedIsmConfig, createRoutingConfig, createTrustedRelayerConfig, } from './ism.js'; @@ -102,13 +107,13 @@ export function isValidWarpRouteDeployConfig(config: any) { export async function createWarpRouteDeployConfig({ context, outPath, - shouldUseDefault = false, + advanced = false, }: { context: CommandContext; outPath: string; - shouldUseDefault: boolean; + advanced: boolean; }) { - logBlue('Creating a new warp route deployment config'); + logBlue('Creating a new warp route deployment config...'); const owner = await detectAndConfirmOrPrompt( async () => context.signer?.getAddress(), @@ -144,9 +149,9 @@ export async function createWarpRouteDeployConfig({ 'hyperlane-registry', ); - const interchainSecurityModule = shouldUseDefault - ? await createDefaultWarpIsmConfig(context) - : await createIsmConfig(context); + const interchainSecurityModule = advanced + ? await createAdvancedIsmConfig(context) + : await createDefaultWarpIsmConfig(context); switch (type) { case TokenType.collateral: @@ -179,12 +184,14 @@ export async function createWarpRouteDeployConfig({ } try { - const parsed = WarpRouteDeployConfigSchema.parse(result); - logGreen(`Warp Route config is valid, writing to file ${outPath}`); - writeYamlOrJson(outPath, parsed); + const warpRouteDeployConfig = WarpRouteDeployConfigSchema.parse(result); + logBlue(`Warp Route config is valid, writing to file ${outPath}:\n`); + log(indentYamlOrJson(yamlStringify(warpRouteDeployConfig, null, 2), 4)); + writeYamlOrJson(outPath, warpRouteDeployConfig, 'yaml'); + logGreen('✅ Successfully created new warp route deployment config.'); } catch (e) { errorRed( - `Warp route deployment config is invalid, please see https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/cli/examples/warp-route-deployment.yaml for an example`, + `Warp route deployment config is invalid, please see https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/cli/examples/warp-route-deployment.yaml for an example.`, ); throw e; } diff --git a/typescript/cli/src/utils/chains.ts b/typescript/cli/src/utils/chains.ts index 8892ce747..dc010b7c7 100644 --- a/typescript/cli/src/utils/chains.ts +++ b/typescript/cli/src/utils/chains.ts @@ -95,5 +95,5 @@ export async function detectAndConfirmOrPrompt( } // eslint-disable-next-line no-empty } catch (e) {} - return input({ message: `${prompt} ${label}`, default: detectedValue }); + return input({ message: `${prompt} ${label}:`, default: detectedValue }); } diff --git a/typescript/cli/src/utils/files.ts b/typescript/cli/src/utils/files.ts index 844eb4b79..277850af6 100644 --- a/typescript/cli/src/utils/files.ts +++ b/typescript/cli/src/utils/files.ts @@ -210,3 +210,11 @@ export async function runFileSelectionStep( if (filename) return filename; else throw new Error(`No filepath entered ${description}`); } + +export function indentYamlOrJson(str: string, indentLevel: number): string { + const indent = ' '.repeat(indentLevel); + return str + .split('\n') + .map((line) => indent + line) + .join('\n'); +} diff --git a/yarn.lock b/yarn.lock index f2cda87c1..6941f708c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5691,8 +5691,8 @@ __metadata: "@aws-sdk/client-kms": "npm:^3.577.0" "@aws-sdk/client-s3": "npm:^3.577.0" "@hyperlane-xyz/registry": "npm:1.3.0" - "@hyperlane-xyz/sdk": "npm:4.0.0-alpha" - "@hyperlane-xyz/utils": "npm:4.0.0-alpha" + "@hyperlane-xyz/sdk": "npm:4.0.0-alpha.0" + "@hyperlane-xyz/utils": "npm:4.0.0-alpha.0" "@inquirer/prompts": "npm:^3.0.0" "@types/mocha": "npm:^10.0.1" "@types/node": "npm:^18.14.5" @@ -5736,12 +5736,12 @@ __metadata: languageName: node linkType: hard -"@hyperlane-xyz/core@npm:4.0.0-alpha, @hyperlane-xyz/core@workspace:solidity": +"@hyperlane-xyz/core@npm:4.0.0-alpha.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:4.0.0-alpha" + "@hyperlane-xyz/utils": "npm:4.0.0-alpha.0" "@layerzerolabs/lz-evm-oapp-v2": "npm:2.0.2" "@layerzerolabs/solidity-examples": "npm:^1.1.0" "@nomiclabs/hardhat-ethers": "npm:^2.2.3" @@ -5773,13 +5773,13 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/helloworld@npm:4.0.0-alpha, @hyperlane-xyz/helloworld@workspace:typescript/helloworld": +"@hyperlane-xyz/helloworld@npm:4.0.0-alpha.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:4.0.0-alpha" + "@hyperlane-xyz/core": "npm:4.0.0-alpha.0" "@hyperlane-xyz/registry": "npm:1.3.0" - "@hyperlane-xyz/sdk": "npm:4.0.0-alpha" + "@hyperlane-xyz/sdk": "npm:4.0.0-alpha.0" "@nomiclabs/hardhat-ethers": "npm:^2.2.3" "@nomiclabs/hardhat-waffle": "npm:^2.0.6" "@openzeppelin/contracts-upgradeable": "npm:^4.9.3" @@ -5824,10 +5824,10 @@ __metadata: "@ethersproject/experimental": "npm:^5.7.0" "@ethersproject/hardware-wallets": "npm:^5.7.0" "@ethersproject/providers": "npm:^5.7.2" - "@hyperlane-xyz/helloworld": "npm:4.0.0-alpha" + "@hyperlane-xyz/helloworld": "npm:4.0.0-alpha.0" "@hyperlane-xyz/registry": "npm:1.3.0" - "@hyperlane-xyz/sdk": "npm:4.0.0-alpha" - "@hyperlane-xyz/utils": "npm:4.0.0-alpha" + "@hyperlane-xyz/sdk": "npm:4.0.0-alpha.0" + "@hyperlane-xyz/utils": "npm:4.0.0-alpha.0" "@nomiclabs/hardhat-ethers": "npm:^2.2.3" "@nomiclabs/hardhat-etherscan": "npm:^3.0.3" "@nomiclabs/hardhat-waffle": "npm:^2.0.6" @@ -5915,15 +5915,15 @@ __metadata: languageName: node linkType: hard -"@hyperlane-xyz/sdk@npm:4.0.0-alpha, @hyperlane-xyz/sdk@workspace:typescript/sdk": +"@hyperlane-xyz/sdk@npm:4.0.0-alpha.0, @hyperlane-xyz/sdk@workspace:typescript/sdk": version: 0.0.0-use.local resolution: "@hyperlane-xyz/sdk@workspace:typescript/sdk" dependencies: "@aws-sdk/client-s3": "npm:^3.74.0" "@cosmjs/cosmwasm-stargate": "npm:^0.31.3" "@cosmjs/stargate": "npm:^0.31.3" - "@hyperlane-xyz/core": "npm:4.0.0-alpha" - "@hyperlane-xyz/utils": "npm:4.0.0-alpha" + "@hyperlane-xyz/core": "npm:4.0.0-alpha.0" + "@hyperlane-xyz/utils": "npm:4.0.0-alpha.0" "@nomiclabs/hardhat-ethers": "npm:^2.2.3" "@nomiclabs/hardhat-waffle": "npm:^2.0.6" "@safe-global/api-kit": "npm:1.3.0" @@ -5975,7 +5975,7 @@ __metadata: languageName: node linkType: hard -"@hyperlane-xyz/utils@npm:4.0.0-alpha, @hyperlane-xyz/utils@workspace:typescript/utils": +"@hyperlane-xyz/utils@npm:4.0.0-alpha.0, @hyperlane-xyz/utils@workspace:typescript/utils": version: 0.0.0-use.local resolution: "@hyperlane-xyz/utils@workspace:typescript/utils" dependencies: From 0e8374f3619d79487ed33d6b6bd9412fa7b5dd24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Noah=20Bayindirli=20=F0=9F=A5=82?= Date: Fri, 7 Jun 2024 19:08:55 -0400 Subject: [PATCH 31/73] fix(cli): set default fallback owner to empty instead of prompting for warp config (#3905) ### Description - sets default fallback owner to empty instead of prompting for warp config ### Drive-by changes - none ### Related issues - fixes https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/3903 ### Backward compatibility - yes ### Testing - ci-test - manual --- typescript/cli/src/config/ism.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/typescript/cli/src/config/ism.ts b/typescript/cli/src/config/ism.ts index 64ae7b099..e2f69b3b5 100644 --- a/typescript/cli/src/config/ism.ts +++ b/typescript/cli/src/config/ism.ts @@ -252,11 +252,6 @@ export const createRoutingConfig = callWithConfigCreationLogs( export const createFallbackRoutingConfig = callWithConfigCreationLogs( async (context: CommandContext): Promise => { - const owner = await input({ - message: 'Enter owner address for fallback routing ISM', - }); - const ownerAddress = owner; - const chains = await runMultiChainSelectionStep( context.chainMetadata, 'Select chains to configure fallback routing ISM for', @@ -273,7 +268,7 @@ export const createFallbackRoutingConfig = callWithConfigCreationLogs( } return { type: IsmType.FALLBACK_ROUTING, - owner: ownerAddress, + owner: '', domains: domainsMap, }; }, From 3283eefd6d870390c0f957e35449557c2151209c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Noah=20Bayindirli=20=F0=9F=A5=82?= Date: Sun, 9 Jun 2024 19:35:33 -0400 Subject: [PATCH 32/73] fix(cli): remove default for chain name (#3909) ### Description - simply prompts if not a lowercase one-word chain name, instead of sometimes returning incorrect chain name ### Drive-by changes - none ### Related issues - none ### Backward compatibility - yes ### Testing - manual w/ caldera: https://early-rabbits-throw.hub.caldera.xyz/ --- .changeset/bright-emus-double.md | 5 +++++ typescript/cli/src/config/chain.ts | 21 +++++++++------------ typescript/sdk/src/index.ts | 2 +- 3 files changed, 15 insertions(+), 13 deletions(-) create mode 100644 .changeset/bright-emus-double.md diff --git a/.changeset/bright-emus-double.md b/.changeset/bright-emus-double.md new file mode 100644 index 000000000..d3c72ef21 --- /dev/null +++ b/.changeset/bright-emus-double.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/cli': patch +--- + +Removes default pattern for chain name when creating a new chain. diff --git a/typescript/cli/src/config/chain.ts b/typescript/cli/src/config/chain.ts index 5c6e756cc..dbda2eb9f 100644 --- a/typescript/cli/src/config/chain.ts +++ b/typescript/cli/src/config/chain.ts @@ -1,7 +1,11 @@ import { confirm, input } from '@inquirer/prompts'; import { ethers } from 'ethers'; -import { ChainMetadata, ChainMetadataSchema } from '@hyperlane-xyz/sdk'; +import { + ChainMetadata, + ChainMetadataSchema, + ZChainName, +} from '@hyperlane-xyz/sdk'; import { ProtocolType } from '@hyperlane-xyz/utils'; import { CommandContext } from '../context/types.js'; @@ -52,17 +56,10 @@ export async function createChainConfig({ ); const provider = new ethers.providers.JsonRpcProvider(rpcUrl); - const name = await detectAndConfirmOrPrompt( - async () => { - const clientName = await provider.send('web3_clientVersion', []); - const port = rpcUrl.split(':').slice(-1); - const client = clientName.split('/')[0]; - return `${client}${port}`; - }, - 'Enter (one word, lower case)', - 'chain name', - 'JSON RPC provider', - ); + const name = await input({ + message: 'Enter chain name (one word, lower case)', + validate: (chainName) => ZChainName.safeParse(chainName).success, + }); const chainId = parseInt( await detectAndConfirmOrPrompt( diff --git a/typescript/sdk/src/index.ts b/typescript/sdk/src/index.ts index c1afe0494..dfafc1bd7 100644 --- a/typescript/sdk/src/index.ts +++ b/typescript/sdk/src/index.ts @@ -155,7 +155,7 @@ export { } from './ism/multisig.js'; export { EvmIsmReader } from './ism/EvmIsmReader.js'; export { collectValidators, moduleCanCertainlyVerify } from './ism/utils.js'; -export { ZHash } from './metadata/customZodTypes.js'; +export { ZChainName, ZHash } from './metadata/customZodTypes.js'; export { BlockExplorer, ChainMetadata, From 4040db723bc03fdf4cd33ddc1453c03955674f92 Mon Sep 17 00:00:00 2001 From: Lee <6251863+ltyu@users.noreply.github.com> Date: Mon, 10 Jun 2024 10:58:18 -0400 Subject: [PATCH 33/73] fix: warp configure infinite loop (#3929) ### Description - Fixes `hyperlane warp configure` ism selection looping by configuring a default Aggregation ISM with trusted relayer and fallback routing ### Related issues - Fixes #3924 ### Backward compatibility Yes ### Testing Manual --- .changeset/sharp-geckos-wash.md | 5 +++++ typescript/cli/src/config/warp.ts | 33 ++++++++++++++++++++----------- 2 files changed, 26 insertions(+), 12 deletions(-) create mode 100644 .changeset/sharp-geckos-wash.md diff --git a/.changeset/sharp-geckos-wash.md b/.changeset/sharp-geckos-wash.md new file mode 100644 index 000000000..88ee03c28 --- /dev/null +++ b/.changeset/sharp-geckos-wash.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/cli': minor +--- + +Fix createDefaultWarpIsmConfig to default to trusted relayer and fallback routing without prompts diff --git a/typescript/cli/src/config/warp.ts b/typescript/cli/src/config/warp.ts index c3de78eff..826622155 100644 --- a/typescript/cli/src/config/warp.ts +++ b/typescript/cli/src/config/warp.ts @@ -12,7 +12,7 @@ import { WarpRouteDeployConfig, WarpRouteDeployConfigSchema, } from '@hyperlane-xyz/sdk'; -import { assert, objMap, promiseObjAll } from '@hyperlane-xyz/utils'; +import { Address, assert, objMap, promiseObjAll } from '@hyperlane-xyz/utils'; import { CommandContext } from '../context/types.js'; import { errorRed, log, logBlue, logGreen } from '../logger.js'; @@ -26,11 +26,7 @@ import { writeYamlOrJson, } from '../utils/files.js'; -import { - createAdvancedIsmConfig, - createRoutingConfig, - createTrustedRelayerConfig, -} from './ism.js'; +import { createAdvancedIsmConfig } from './ism.js'; const TYPE_DESCRIPTIONS: Record = { [TokenType.synthetic]: 'A new ERC20 with remote transfer functionality', @@ -151,7 +147,7 @@ export async function createWarpRouteDeployConfig({ const interchainSecurityModule = advanced ? await createAdvancedIsmConfig(context) - : await createDefaultWarpIsmConfig(context); + : createDefaultWarpIsmConfig(owner); switch (type) { case TokenType.collateral: @@ -205,14 +201,27 @@ export function readWarpRouteConfig(filePath: string): WarpCoreConfig { return WarpCoreConfigSchema.parse(config); } -async function createDefaultWarpIsmConfig( - context: CommandContext, -): Promise { +/** + * Creates a default configuration for an ISM with a TRUSTED_RELAYER and FALLBACK_ROUTING. + * + * Properties relayer and owner are both set as input owner. + * + * @param owner - The address of the owner of the ISM. + * @returns The default Aggregation ISM configuration. + */ +function createDefaultWarpIsmConfig(owner: Address): IsmConfig { return { type: IsmType.AGGREGATION, modules: [ - await createTrustedRelayerConfig(context), - await createRoutingConfig(context, IsmType.FALLBACK_ROUTING), + { + type: IsmType.TRUSTED_RELAYER, + relayer: owner, + }, + { + type: IsmType.FALLBACK_ROUTING, + domains: {}, + owner, + }, ], threshold: 1, }; From 6b63c5d823baa99b029adb1d6bd07d6db53ce4ef Mon Sep 17 00:00:00 2001 From: Lee <6251863+ltyu@users.noreply.github.com> Date: Tue, 11 Jun 2024 18:14:46 -0400 Subject: [PATCH 34/73] feat: add deploy support for IsmConfig in WarpConfig (#3939) ### Description Adds deployment support for IsmConfig within a WarpRouteConfig ### Drive-by changes - Update dry-run confirmation to 1 ### Related issues - Fixes https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/3933 ### Backward compatibility Yes ### Testing Manual - [x] Deploy Warp with Ism as object - [x] Deploy Warp with Ism as string - [x] Deploy Warp with Ism as empty - [x] Deploy synthetic on sepolia and collateral holesky with default ism config (aggregation, trusted, domain) - [x] Deploy collateral on sepolia with default ism config (aggregation, trusted, domain) --- .changeset/late-rings-attack.md | 6 + typescript/cli/src/deploy/dry-run.ts | 2 +- typescript/cli/src/deploy/warp.ts | 131 +++++++++++++++++- typescript/sdk/src/index.ts | 1 + typescript/sdk/src/ism/EvmIsmModule.ts | 15 +- .../token/EvmERC20WarpModule.hardhat-test.ts | 7 +- typescript/sdk/src/token/deploy.ts | 4 - 7 files changed, 147 insertions(+), 19 deletions(-) create mode 100644 .changeset/late-rings-attack.md diff --git a/.changeset/late-rings-attack.md b/.changeset/late-rings-attack.md new file mode 100644 index 000000000..613f32aa3 --- /dev/null +++ b/.changeset/late-rings-attack.md @@ -0,0 +1,6 @@ +--- +'@hyperlane-xyz/cli': patch +'@hyperlane-xyz/sdk': patch +--- + +Adds deployment support for IsmConfig within a WarpRouteConfig diff --git a/typescript/cli/src/deploy/dry-run.ts b/typescript/cli/src/deploy/dry-run.ts index a2c1fb259..f821dc179 100644 --- a/typescript/cli/src/deploy/dry-run.ts +++ b/typescript/cli/src/deploy/dry-run.ts @@ -21,7 +21,7 @@ export async function forkNetworkToMultiProvider( chain: string, ) { multiProvider = multiProvider.extendChainMetadata({ - [chain]: { blocks: { confirmations: 0 } }, + [chain]: { blocks: { confirmations: 1 } }, }); await setFork(multiProvider, chain); diff --git a/typescript/cli/src/deploy/warp.ts b/typescript/cli/src/deploy/warp.ts index 0968f0d99..6af9f6758 100644 --- a/typescript/cli/src/deploy/warp.ts +++ b/typescript/cli/src/deploy/warp.ts @@ -1,9 +1,16 @@ import { confirm } from '@inquirer/prompts'; +import { stringify as yamlStringify } from 'yaml'; +import { IRegistry } from '@hyperlane-xyz/registry'; import { + EvmIsmModule, HypERC20Deployer, HypERC721Deployer, + HyperlaneAddresses, HyperlaneContractsMap, + HyperlaneDeployer, + HyperlaneProxyFactoryDeployer, + MultiProvider, TOKEN_TYPE_TO_STANDARD, TokenFactories, TokenType, @@ -11,14 +18,19 @@ import { WarpRouteDeployConfig, getTokenConnectionId, isTokenMetadata, + serializeContracts, } from '@hyperlane-xyz/sdk'; -import { ProtocolType } from '@hyperlane-xyz/utils'; +import { ProtocolType, objMap, promiseObjAll } from '@hyperlane-xyz/utils'; import { readWarpRouteDeployConfig } from '../config/warp.js'; import { MINIMUM_WARP_DEPLOY_GAS } from '../consts.js'; import { WriteCommandContext } from '../context/types.js'; import { log, logBlue, logGray, logGreen, logTable } from '../logger.js'; -import { isFile, runFileSelectionStep } from '../utils/files.js'; +import { + indentYamlOrJson, + isFile, + runFileSelectionStep, +} from '../utils/files.js'; import { completeDeploy, @@ -119,7 +131,18 @@ async function executeDeploy(params: DeployParams) { ? { [dryRunChain]: configMap[dryRunChain] } : configMap; - const deployedContracts = await deployer.deploy(config); + const ismFactoryDeployer = new HyperlaneProxyFactoryDeployer(multiProvider); + + // For each chain in WarpRouteConfig, deploy each Ism Factory, if it's not in the registry + // Then return a modified config with the ism address as a string + const modifiedConfig = await deployAndResolveWarpIsm( + config, + multiProvider, + registry, + ismFactoryDeployer, + ); + + const deployedContracts = await deployer.deploy(modifiedConfig); logGreen('✅ Hyp token deployments complete'); @@ -128,10 +151,110 @@ async function executeDeploy(params: DeployParams) { log('Writing deployment artifacts'); await registry.addWarpRoute(warpCoreConfig); } - log(JSON.stringify(warpCoreConfig, null, 2)); + log(indentYamlOrJson(yamlStringify(warpCoreConfig, null, 2), 4)); logBlue('Deployment is complete!'); } +async function deployAndResolveWarpIsm( + warpConfig: WarpRouteDeployConfig, + multiProvider: MultiProvider, + registry: IRegistry, + ismFactoryDeployer: HyperlaneProxyFactoryDeployer, +): Promise { + return promiseObjAll( + objMap(warpConfig, async (chain, config) => { + // Skip deployment if Ism is empty, or a string + if ( + !config.interchainSecurityModule || + typeof config.interchainSecurityModule === 'string' + ) { + logGray( + `Config Ism is ${ + !config.interchainSecurityModule + ? 'empty' + : config.interchainSecurityModule + }, skipping deployment`, + ); + return config; + } + + logBlue('Loading Registry factory addresses'); + let chainAddresses = await registry.getChainAddresses(chain); // Can includes other addresses + + if (!chainAddresses) { + logGray('Registry factory addresses not found, deploying'); + chainAddresses = serializeContracts( + await ismFactoryDeployer.deployContracts(chain), + ) as Record; + } + + logGray( + `Creating ${config.interchainSecurityModule.type} Ism for ${config.type} token on ${chain} chain`, + ); + + const deployedIsm = await createWarpIsm( + chain, + warpConfig, + multiProvider, + ismFactoryDeployer, + { + domainRoutingIsmFactory: chainAddresses.domainRoutingIsmFactory, + staticAggregationHookFactory: + chainAddresses.staticAggregationHookFactory, + staticAggregationIsmFactory: + chainAddresses.staticAggregationIsmFactory, + staticMerkleRootMultisigIsmFactory: + chainAddresses.staticMerkleRootMultisigIsmFactory, + staticMessageIdMultisigIsmFactory: + chainAddresses.staticMessageIdMultisigIsmFactory, + }, + ); + + logGreen( + `Finished creating ${config.interchainSecurityModule.type} Ism for ${config.type} token on ${chain} chain`, + ); + return { ...warpConfig[chain], interchainSecurityModule: deployedIsm }; + }), + ); +} + +/** + * Deploys the Warp ISM for a given config + * + * @returns The deployed ism address + */ +async function createWarpIsm( + chain: string, + warpConfig: WarpRouteDeployConfig, + multiProvider: MultiProvider, + ismFactoryDeployer: HyperlaneDeployer, + factoryAddresses: HyperlaneAddresses, +): Promise { + const { + domainRoutingIsmFactory, + staticAggregationHookFactory, + staticAggregationIsmFactory, + staticMerkleRootMultisigIsmFactory, + staticMessageIdMultisigIsmFactory, + } = factoryAddresses; + const evmIsmModule = await EvmIsmModule.create({ + chain, + multiProvider, + deployer: ismFactoryDeployer, + mailbox: warpConfig[chain].mailbox, + factories: { + domainRoutingIsmFactory, + staticAggregationHookFactory, + staticAggregationIsmFactory, + staticMerkleRootMultisigIsmFactory, + staticMessageIdMultisigIsmFactory, + }, + config: warpConfig[chain].interchainSecurityModule!, + }); + const { deployedIsm } = evmIsmModule.serialize(); + return deployedIsm; +} + async function getWarpCoreConfig( { configMap, context }: DeployParams, contracts: HyperlaneContractsMap, diff --git a/typescript/sdk/src/index.ts b/typescript/sdk/src/index.ts index dfafc1bd7..6e679b79b 100644 --- a/typescript/sdk/src/index.ts +++ b/typescript/sdk/src/index.ts @@ -507,3 +507,4 @@ export { canProposeSafeTransactions, getSafe, getSafeDelegates, getSafeService } export { EvmCoreModule, DeployedCoreAdresses } from './core/EvmCoreModule.js'; export { EvmERC20WarpModule } from './token/EvmERC20WarpModule.js'; +export { EvmIsmModule } from './ism/EvmIsmModule.js'; diff --git a/typescript/sdk/src/ism/EvmIsmModule.ts b/typescript/sdk/src/ism/EvmIsmModule.ts index 5664687ed..e4c6db641 100644 --- a/typescript/sdk/src/ism/EvmIsmModule.ts +++ b/typescript/sdk/src/ism/EvmIsmModule.ts @@ -468,12 +468,13 @@ export class EvmIsmModule extends HyperlaneModule< // initialize the fallback routing ISM logger.debug('Initializing fallback routing ISM ...'); - await ism['initialize(address,uint32[],address[])']( + const tx = await ism['initialize(address,uint32[],address[])']( config.owner, availableDomainIds, submoduleAddresses, ); + await this.multiProvider.handleTx(this.chain, tx); // return the fallback routing ISM return ism; } @@ -536,12 +537,12 @@ export class EvmIsmModule extends HyperlaneModule< config: AggregationIsmConfig; logger: Logger; }): Promise { - const addresses: Address[] = await Promise.all( - config.modules.map(async (module) => { - const submodule = await this.deploy({ config: module }); - return submodule.address; - }), - ); + const addresses: Address[] = []; + // Needs to be deployed sequentially because Ethers will throw `Error: replacement fee too low` + for (const module of config.modules) { + const submodule = await this.deploy({ config: module }); + addresses.push(submodule.address); + } const factoryName = 'staticAggregationIsmFactory'; const address = await EvmIsmModule.deployStaticAddressSet({ diff --git a/typescript/sdk/src/token/EvmERC20WarpModule.hardhat-test.ts b/typescript/sdk/src/token/EvmERC20WarpModule.hardhat-test.ts index a20ce71e3..0a60e2dcb 100644 --- a/typescript/sdk/src/token/EvmERC20WarpModule.hardhat-test.ts +++ b/typescript/sdk/src/token/EvmERC20WarpModule.hardhat-test.ts @@ -57,6 +57,7 @@ describe('EvmERC20WarpHyperlaneModule', async () => { ); expect(await deployedToken.owner()).to.equal(signer.address); } + before(async () => { [signer] = await hre.ethers.getSigners(); multiProvider = MultiProvider.createTestMultiProvider({ signer }); @@ -82,7 +83,7 @@ describe('EvmERC20WarpHyperlaneModule', async () => { hookAddress = await mailbox.defaultHook(); }); - it('should create with a a collateral config', async () => { + it('should create with a collateral config', async () => { const config = { ...baseConfig, type: TokenType.collateral, @@ -144,7 +145,7 @@ describe('EvmERC20WarpHyperlaneModule', async () => { ); }); - it('should create with a a synthetic config', async () => { + it('should create with a synthetic config', async () => { const config = { type: TokenType.synthetic, token: token.address, @@ -181,7 +182,7 @@ describe('EvmERC20WarpHyperlaneModule', async () => { expect(await syntheticContract.totalSupply()).to.equal(TOKEN_SUPPLY); }); - it('should create with a a native config', async () => { + it('should create with a native config', async () => { const config = { type: TokenType.native, hook: hookAddress, diff --git a/typescript/sdk/src/token/deploy.ts b/typescript/sdk/src/token/deploy.ts index ad865c4f0..5dfa37250 100644 --- a/typescript/sdk/src/token/deploy.ts +++ b/typescript/sdk/src/token/deploy.ts @@ -65,10 +65,6 @@ abstract class TokenDeployer< } async initializeArgs(_: ChainName, config: TokenRouterConfig): Promise { - // ISM config can be an object, but is not supported right now. - if (typeof config.interchainSecurityModule === 'object') { - throw new Error('Token deployer does not support ISM objects currently'); - } const defaultArgs = [ config.hook ?? constants.AddressZero, config.interchainSecurityModule ?? constants.AddressZero, From 5ed86c6168b58b921555100808e2aa888ed7151e Mon Sep 17 00:00:00 2001 From: Connor McEwen Date: Tue, 11 Jun 2024 18:40:10 -0400 Subject: [PATCH 35/73] chore: new alpha build --- .changeset/pre.json | 17 ++++++++++------- solidity/CHANGELOG.md | 6 ++++++ solidity/package.json | 4 ++-- typescript/ccip-server/CHANGELOG.md | 2 ++ typescript/ccip-server/package.json | 2 +- typescript/cli/CHANGELOG.md | 14 ++++++++++++++ typescript/cli/package.json | 6 +++--- typescript/helloworld/CHANGELOG.md | 8 ++++++++ typescript/helloworld/package.json | 6 +++--- typescript/infra/CHANGELOG.md | 9 +++++++++ typescript/infra/package.json | 8 ++++---- typescript/sdk/CHANGELOG.md | 8 ++++++++ typescript/sdk/package.json | 6 +++--- typescript/utils/CHANGELOG.md | 2 ++ typescript/utils/package.json | 2 +- 15 files changed, 76 insertions(+), 24 deletions(-) diff --git a/.changeset/pre.json b/.changeset/pre.json index 6842eab03..741a3c3c6 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -2,16 +2,19 @@ "mode": "pre", "tag": "alpha", "initialVersions": { - "@hyperlane-xyz/core": "4.0.0-alpha", - "@hyperlane-xyz/ccip-server": "4.0.0-alpha", - "@hyperlane-xyz/cli": "4.0.0-alpha", - "@hyperlane-xyz/helloworld": "4.0.0-alpha", - "@hyperlane-xyz/infra": "4.0.0-alpha", - "@hyperlane-xyz/sdk": "4.0.0-alpha", - "@hyperlane-xyz/utils": "4.0.0-alpha" + "@hyperlane-xyz/core": "4.0.0-alpha.0", + "@hyperlane-xyz/ccip-server": "4.0.0-alpha.0", + "@hyperlane-xyz/cli": "4.0.0-alpha.0", + "@hyperlane-xyz/helloworld": "4.0.0-alpha.0", + "@hyperlane-xyz/infra": "4.0.0-alpha.0", + "@hyperlane-xyz/sdk": "4.0.0-alpha.0", + "@hyperlane-xyz/utils": "4.0.0-alpha.0" }, "changesets": [ + "bright-emus-double", "five-baboons-smoke", + "late-rings-attack", + "sharp-geckos-wash", "slimy-toys-argue" ] } diff --git a/solidity/CHANGELOG.md b/solidity/CHANGELOG.md index 25b96c929..d733e30c6 100644 --- a/solidity/CHANGELOG.md +++ b/solidity/CHANGELOG.md @@ -1,5 +1,11 @@ # @hyperlane-xyz/core +## 4.0.0-alpha.1 + +### Patch Changes + +- @hyperlane-xyz/utils@4.0.0-alpha.1 + ## 4.0.0-alpha.0 ### Patch Changes diff --git a/solidity/package.json b/solidity/package.json index 14451bf5d..a2091955d 100644 --- a/solidity/package.json +++ b/solidity/package.json @@ -1,10 +1,10 @@ { "name": "@hyperlane-xyz/core", "description": "Core solidity contracts for Hyperlane", - "version": "4.0.0-alpha.0", + "version": "4.0.0-alpha.1", "dependencies": { "@eth-optimism/contracts": "^0.6.0", - "@hyperlane-xyz/utils": "4.0.0-alpha.0", + "@hyperlane-xyz/utils": "4.0.0-alpha.1", "@layerzerolabs/lz-evm-oapp-v2": "2.0.2", "@openzeppelin/contracts": "^4.9.3", "@openzeppelin/contracts-upgradeable": "^v4.9.3", diff --git a/typescript/ccip-server/CHANGELOG.md b/typescript/ccip-server/CHANGELOG.md index d9b7b80a1..e71800b68 100644 --- a/typescript/ccip-server/CHANGELOG.md +++ b/typescript/ccip-server/CHANGELOG.md @@ -1,5 +1,7 @@ # @hyperlane-xyz/ccip-server +## 4.0.0-alpha.1 + ## 4.0.0-alpha.0 ## 4.0.0-alpha diff --git a/typescript/ccip-server/package.json b/typescript/ccip-server/package.json index 181b27578..f24eb5e2b 100644 --- a/typescript/ccip-server/package.json +++ b/typescript/ccip-server/package.json @@ -1,6 +1,6 @@ { "name": "@hyperlane-xyz/ccip-server", - "version": "4.0.0-alpha.0", + "version": "4.0.0-alpha.1", "description": "CCIP server", "typings": "dist/index.d.ts", "typedocMain": "src/index.ts", diff --git a/typescript/cli/CHANGELOG.md b/typescript/cli/CHANGELOG.md index c7a67eca0..eeaae3231 100644 --- a/typescript/cli/CHANGELOG.md +++ b/typescript/cli/CHANGELOG.md @@ -1,5 +1,19 @@ # @hyperlane-xyz/cli +## 4.0.0-alpha.1 + +### Minor Changes + +- 4040db723: Fix createDefaultWarpIsmConfig to default to trusted relayer and fallback routing without prompts + +### Patch Changes + +- 3283eefd6: Removes default pattern for chain name when creating a new chain. +- 6b63c5d82: Adds deployment support for IsmConfig within a WarpRouteConfig +- Updated dependencies [6b63c5d82] + - @hyperlane-xyz/sdk@4.0.0-alpha.1 + - @hyperlane-xyz/utils@4.0.0-alpha.1 + ## 4.0.0-alpha.0 ### Minor Changes diff --git a/typescript/cli/package.json b/typescript/cli/package.json index 6ea22877c..e6336e6e4 100644 --- a/typescript/cli/package.json +++ b/typescript/cli/package.json @@ -1,13 +1,13 @@ { "name": "@hyperlane-xyz/cli", - "version": "4.0.0-alpha.0", + "version": "4.0.0-alpha.1", "description": "A command-line utility for common Hyperlane operations", "dependencies": { "@aws-sdk/client-kms": "^3.577.0", "@aws-sdk/client-s3": "^3.577.0", "@hyperlane-xyz/registry": "1.3.0", - "@hyperlane-xyz/sdk": "4.0.0-alpha.0", - "@hyperlane-xyz/utils": "4.0.0-alpha.0", + "@hyperlane-xyz/sdk": "4.0.0-alpha.1", + "@hyperlane-xyz/utils": "4.0.0-alpha.1", "@inquirer/prompts": "^3.0.0", "asn1.js": "^5.4.1", "bignumber.js": "^9.1.1", diff --git a/typescript/helloworld/CHANGELOG.md b/typescript/helloworld/CHANGELOG.md index 82b963cdd..420908650 100644 --- a/typescript/helloworld/CHANGELOG.md +++ b/typescript/helloworld/CHANGELOG.md @@ -1,5 +1,13 @@ # @hyperlane-xyz/helloworld +## 4.0.0-alpha.1 + +### Patch Changes + +- Updated dependencies [6b63c5d82] + - @hyperlane-xyz/sdk@4.0.0-alpha.1 + - @hyperlane-xyz/core@4.0.0-alpha.1 + ## 4.0.0-alpha.0 ### Patch Changes diff --git a/typescript/helloworld/package.json b/typescript/helloworld/package.json index f1b071ce1..a46dabe49 100644 --- a/typescript/helloworld/package.json +++ b/typescript/helloworld/package.json @@ -1,11 +1,11 @@ { "name": "@hyperlane-xyz/helloworld", "description": "A basic skeleton of an Hyperlane app", - "version": "4.0.0-alpha.0", + "version": "4.0.0-alpha.1", "dependencies": { - "@hyperlane-xyz/core": "4.0.0-alpha.0", + "@hyperlane-xyz/core": "4.0.0-alpha.1", "@hyperlane-xyz/registry": "1.3.0", - "@hyperlane-xyz/sdk": "4.0.0-alpha.0", + "@hyperlane-xyz/sdk": "4.0.0-alpha.1", "@openzeppelin/contracts-upgradeable": "^4.9.3", "ethers": "^5.7.2" }, diff --git a/typescript/infra/CHANGELOG.md b/typescript/infra/CHANGELOG.md index 0c783da25..db5db6363 100644 --- a/typescript/infra/CHANGELOG.md +++ b/typescript/infra/CHANGELOG.md @@ -1,5 +1,14 @@ # @hyperlane-xyz/infra +## 4.0.0-alpha.1 + +### Patch Changes + +- Updated dependencies [6b63c5d82] + - @hyperlane-xyz/sdk@4.0.0-alpha.1 + - @hyperlane-xyz/helloworld@4.0.0-alpha.1 + - @hyperlane-xyz/utils@4.0.0-alpha.1 + ## 4.0.0-alpha.0 ### Patch Changes diff --git a/typescript/infra/package.json b/typescript/infra/package.json index ef420d668..61dbfb2ab 100644 --- a/typescript/infra/package.json +++ b/typescript/infra/package.json @@ -1,7 +1,7 @@ { "name": "@hyperlane-xyz/infra", "description": "Infrastructure utilities for the Hyperlane Network", - "version": "4.0.0-alpha.0", + "version": "4.0.0-alpha.1", "dependencies": { "@arbitrum/sdk": "^3.0.0", "@aws-sdk/client-iam": "^3.74.0", @@ -12,10 +12,10 @@ "@ethersproject/experimental": "^5.7.0", "@ethersproject/hardware-wallets": "^5.7.0", "@ethersproject/providers": "^5.7.2", - "@hyperlane-xyz/helloworld": "4.0.0-alpha.0", + "@hyperlane-xyz/helloworld": "4.0.0-alpha.1", "@hyperlane-xyz/registry": "1.3.0", - "@hyperlane-xyz/sdk": "4.0.0-alpha.0", - "@hyperlane-xyz/utils": "4.0.0-alpha.0", + "@hyperlane-xyz/sdk": "4.0.0-alpha.1", + "@hyperlane-xyz/utils": "4.0.0-alpha.1", "@nomiclabs/hardhat-etherscan": "^3.0.3", "@solana/web3.js": "^1.78.0", "asn1.js": "5.4.1", diff --git a/typescript/sdk/CHANGELOG.md b/typescript/sdk/CHANGELOG.md index efcec9c94..983fd80c3 100644 --- a/typescript/sdk/CHANGELOG.md +++ b/typescript/sdk/CHANGELOG.md @@ -1,5 +1,13 @@ # @hyperlane-xyz/sdk +## 4.0.0-alpha.1 + +### Patch Changes + +- 6b63c5d82: Adds deployment support for IsmConfig within a WarpRouteConfig + - @hyperlane-xyz/core@4.0.0-alpha.1 + - @hyperlane-xyz/utils@4.0.0-alpha.1 + ## 4.0.0-alpha.0 ### Minor Changes diff --git a/typescript/sdk/package.json b/typescript/sdk/package.json index 2eed8cbf3..d6bb3ee16 100644 --- a/typescript/sdk/package.json +++ b/typescript/sdk/package.json @@ -1,13 +1,13 @@ { "name": "@hyperlane-xyz/sdk", "description": "The official SDK for the Hyperlane Network", - "version": "4.0.0-alpha.0", + "version": "4.0.0-alpha.1", "dependencies": { "@aws-sdk/client-s3": "^3.74.0", "@cosmjs/cosmwasm-stargate": "^0.31.3", "@cosmjs/stargate": "^0.31.3", - "@hyperlane-xyz/core": "4.0.0-alpha.0", - "@hyperlane-xyz/utils": "4.0.0-alpha.0", + "@hyperlane-xyz/core": "4.0.0-alpha.1", + "@hyperlane-xyz/utils": "4.0.0-alpha.1", "@safe-global/api-kit": "1.3.0", "@safe-global/protocol-kit": "1.3.0", "@solana/spl-token": "^0.3.8", diff --git a/typescript/utils/CHANGELOG.md b/typescript/utils/CHANGELOG.md index 356c4d74c..df1a73e1f 100644 --- a/typescript/utils/CHANGELOG.md +++ b/typescript/utils/CHANGELOG.md @@ -1,5 +1,7 @@ # @hyperlane-xyz/utils +## 4.0.0-alpha.1 + ## 4.0.0-alpha.0 ## 4.0.0-alpha diff --git a/typescript/utils/package.json b/typescript/utils/package.json index 579b5e306..5b4235e32 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": "4.0.0-alpha.0", + "version": "4.0.0-alpha.1", "dependencies": { "@cosmjs/encoding": "^0.31.3", "@solana/web3.js": "^1.78.0", From 6d30eed2bbd351855db70ae8b98fb11a791e1431 Mon Sep 17 00:00:00 2001 From: Connor McEwen Date: Tue, 11 Jun 2024 18:46:08 -0400 Subject: [PATCH 36/73] chore: alpha.2 --- solidity/CHANGELOG.md | 4 ++-- solidity/package.json | 4 ++-- typescript/ccip-server/CHANGELOG.md | 2 +- typescript/ccip-server/package.json | 2 +- typescript/cli/CHANGELOG.md | 6 +++--- typescript/cli/package.json | 6 +++--- typescript/cli/src/version.ts | 2 +- typescript/helloworld/CHANGELOG.md | 6 +++--- typescript/helloworld/package.json | 6 +++--- typescript/infra/CHANGELOG.md | 8 ++++---- typescript/infra/package.json | 8 ++++---- typescript/sdk/CHANGELOG.md | 6 +++--- typescript/sdk/package.json | 6 +++--- typescript/utils/CHANGELOG.md | 2 +- typescript/utils/package.json | 2 +- yarn.lock | 28 ++++++++++++++-------------- 16 files changed, 49 insertions(+), 49 deletions(-) diff --git a/solidity/CHANGELOG.md b/solidity/CHANGELOG.md index d733e30c6..49a969e8d 100644 --- a/solidity/CHANGELOG.md +++ b/solidity/CHANGELOG.md @@ -1,10 +1,10 @@ # @hyperlane-xyz/core -## 4.0.0-alpha.1 +## 4.0.0-alpha.2 ### Patch Changes -- @hyperlane-xyz/utils@4.0.0-alpha.1 +- @hyperlane-xyz/utils@4.0.0-alpha.2 ## 4.0.0-alpha.0 diff --git a/solidity/package.json b/solidity/package.json index a2091955d..48e2e1926 100644 --- a/solidity/package.json +++ b/solidity/package.json @@ -1,10 +1,10 @@ { "name": "@hyperlane-xyz/core", "description": "Core solidity contracts for Hyperlane", - "version": "4.0.0-alpha.1", + "version": "4.0.0-alpha.2", "dependencies": { "@eth-optimism/contracts": "^0.6.0", - "@hyperlane-xyz/utils": "4.0.0-alpha.1", + "@hyperlane-xyz/utils": "4.0.0-alpha.2", "@layerzerolabs/lz-evm-oapp-v2": "2.0.2", "@openzeppelin/contracts": "^4.9.3", "@openzeppelin/contracts-upgradeable": "^v4.9.3", diff --git a/typescript/ccip-server/CHANGELOG.md b/typescript/ccip-server/CHANGELOG.md index e71800b68..dfe9ec8e7 100644 --- a/typescript/ccip-server/CHANGELOG.md +++ b/typescript/ccip-server/CHANGELOG.md @@ -1,6 +1,6 @@ # @hyperlane-xyz/ccip-server -## 4.0.0-alpha.1 +## 4.0.0-alpha.2 ## 4.0.0-alpha.0 diff --git a/typescript/ccip-server/package.json b/typescript/ccip-server/package.json index f24eb5e2b..eb64fae8f 100644 --- a/typescript/ccip-server/package.json +++ b/typescript/ccip-server/package.json @@ -1,6 +1,6 @@ { "name": "@hyperlane-xyz/ccip-server", - "version": "4.0.0-alpha.1", + "version": "4.0.0-alpha.2", "description": "CCIP server", "typings": "dist/index.d.ts", "typedocMain": "src/index.ts", diff --git a/typescript/cli/CHANGELOG.md b/typescript/cli/CHANGELOG.md index eeaae3231..19cdd7ead 100644 --- a/typescript/cli/CHANGELOG.md +++ b/typescript/cli/CHANGELOG.md @@ -1,6 +1,6 @@ # @hyperlane-xyz/cli -## 4.0.0-alpha.1 +## 4.0.0-alpha.2 ### Minor Changes @@ -11,8 +11,8 @@ - 3283eefd6: Removes default pattern for chain name when creating a new chain. - 6b63c5d82: Adds deployment support for IsmConfig within a WarpRouteConfig - Updated dependencies [6b63c5d82] - - @hyperlane-xyz/sdk@4.0.0-alpha.1 - - @hyperlane-xyz/utils@4.0.0-alpha.1 + - @hyperlane-xyz/sdk@4.0.0-alpha.2 + - @hyperlane-xyz/utils@4.0.0-alpha.2 ## 4.0.0-alpha.0 diff --git a/typescript/cli/package.json b/typescript/cli/package.json index e6336e6e4..ed84f1155 100644 --- a/typescript/cli/package.json +++ b/typescript/cli/package.json @@ -1,13 +1,13 @@ { "name": "@hyperlane-xyz/cli", - "version": "4.0.0-alpha.1", + "version": "4.0.0-alpha.2", "description": "A command-line utility for common Hyperlane operations", "dependencies": { "@aws-sdk/client-kms": "^3.577.0", "@aws-sdk/client-s3": "^3.577.0", "@hyperlane-xyz/registry": "1.3.0", - "@hyperlane-xyz/sdk": "4.0.0-alpha.1", - "@hyperlane-xyz/utils": "4.0.0-alpha.1", + "@hyperlane-xyz/sdk": "4.0.0-alpha.2", + "@hyperlane-xyz/utils": "4.0.0-alpha.2", "@inquirer/prompts": "^3.0.0", "asn1.js": "^5.4.1", "bignumber.js": "^9.1.1", diff --git a/typescript/cli/src/version.ts b/typescript/cli/src/version.ts index 3a8af1c6d..eef5a6f42 100644 --- a/typescript/cli/src/version.ts +++ b/typescript/cli/src/version.ts @@ -1 +1 @@ -export const VERSION = '4.0.0-alpha.0'; +export const VERSION = '4.0.0-alpha.2'; diff --git a/typescript/helloworld/CHANGELOG.md b/typescript/helloworld/CHANGELOG.md index 420908650..47f1f8be2 100644 --- a/typescript/helloworld/CHANGELOG.md +++ b/typescript/helloworld/CHANGELOG.md @@ -1,12 +1,12 @@ # @hyperlane-xyz/helloworld -## 4.0.0-alpha.1 +## 4.0.0-alpha.2 ### Patch Changes - Updated dependencies [6b63c5d82] - - @hyperlane-xyz/sdk@4.0.0-alpha.1 - - @hyperlane-xyz/core@4.0.0-alpha.1 + - @hyperlane-xyz/sdk@4.0.0-alpha.2 + - @hyperlane-xyz/core@4.0.0-alpha.2 ## 4.0.0-alpha.0 diff --git a/typescript/helloworld/package.json b/typescript/helloworld/package.json index a46dabe49..e10f34329 100644 --- a/typescript/helloworld/package.json +++ b/typescript/helloworld/package.json @@ -1,11 +1,11 @@ { "name": "@hyperlane-xyz/helloworld", "description": "A basic skeleton of an Hyperlane app", - "version": "4.0.0-alpha.1", + "version": "4.0.0-alpha.2", "dependencies": { - "@hyperlane-xyz/core": "4.0.0-alpha.1", + "@hyperlane-xyz/core": "4.0.0-alpha.2", "@hyperlane-xyz/registry": "1.3.0", - "@hyperlane-xyz/sdk": "4.0.0-alpha.1", + "@hyperlane-xyz/sdk": "4.0.0-alpha.2", "@openzeppelin/contracts-upgradeable": "^4.9.3", "ethers": "^5.7.2" }, diff --git a/typescript/infra/CHANGELOG.md b/typescript/infra/CHANGELOG.md index db5db6363..6abcbec1b 100644 --- a/typescript/infra/CHANGELOG.md +++ b/typescript/infra/CHANGELOG.md @@ -1,13 +1,13 @@ # @hyperlane-xyz/infra -## 4.0.0-alpha.1 +## 4.0.0-alpha.2 ### Patch Changes - Updated dependencies [6b63c5d82] - - @hyperlane-xyz/sdk@4.0.0-alpha.1 - - @hyperlane-xyz/helloworld@4.0.0-alpha.1 - - @hyperlane-xyz/utils@4.0.0-alpha.1 + - @hyperlane-xyz/sdk@4.0.0-alpha.2 + - @hyperlane-xyz/helloworld@4.0.0-alpha.2 + - @hyperlane-xyz/utils@4.0.0-alpha.2 ## 4.0.0-alpha.0 diff --git a/typescript/infra/package.json b/typescript/infra/package.json index 61dbfb2ab..7d97bf316 100644 --- a/typescript/infra/package.json +++ b/typescript/infra/package.json @@ -1,7 +1,7 @@ { "name": "@hyperlane-xyz/infra", "description": "Infrastructure utilities for the Hyperlane Network", - "version": "4.0.0-alpha.1", + "version": "4.0.0-alpha.2", "dependencies": { "@arbitrum/sdk": "^3.0.0", "@aws-sdk/client-iam": "^3.74.0", @@ -12,10 +12,10 @@ "@ethersproject/experimental": "^5.7.0", "@ethersproject/hardware-wallets": "^5.7.0", "@ethersproject/providers": "^5.7.2", - "@hyperlane-xyz/helloworld": "4.0.0-alpha.1", + "@hyperlane-xyz/helloworld": "4.0.0-alpha.2", "@hyperlane-xyz/registry": "1.3.0", - "@hyperlane-xyz/sdk": "4.0.0-alpha.1", - "@hyperlane-xyz/utils": "4.0.0-alpha.1", + "@hyperlane-xyz/sdk": "4.0.0-alpha.2", + "@hyperlane-xyz/utils": "4.0.0-alpha.2", "@nomiclabs/hardhat-etherscan": "^3.0.3", "@solana/web3.js": "^1.78.0", "asn1.js": "5.4.1", diff --git a/typescript/sdk/CHANGELOG.md b/typescript/sdk/CHANGELOG.md index 983fd80c3..f7b5bff47 100644 --- a/typescript/sdk/CHANGELOG.md +++ b/typescript/sdk/CHANGELOG.md @@ -1,12 +1,12 @@ # @hyperlane-xyz/sdk -## 4.0.0-alpha.1 +## 4.0.0-alpha.2 ### Patch Changes - 6b63c5d82: Adds deployment support for IsmConfig within a WarpRouteConfig - - @hyperlane-xyz/core@4.0.0-alpha.1 - - @hyperlane-xyz/utils@4.0.0-alpha.1 + - @hyperlane-xyz/core@4.0.0-alpha.2 + - @hyperlane-xyz/utils@4.0.0-alpha.2 ## 4.0.0-alpha.0 diff --git a/typescript/sdk/package.json b/typescript/sdk/package.json index d6bb3ee16..3944b8f92 100644 --- a/typescript/sdk/package.json +++ b/typescript/sdk/package.json @@ -1,13 +1,13 @@ { "name": "@hyperlane-xyz/sdk", "description": "The official SDK for the Hyperlane Network", - "version": "4.0.0-alpha.1", + "version": "4.0.0-alpha.2", "dependencies": { "@aws-sdk/client-s3": "^3.74.0", "@cosmjs/cosmwasm-stargate": "^0.31.3", "@cosmjs/stargate": "^0.31.3", - "@hyperlane-xyz/core": "4.0.0-alpha.1", - "@hyperlane-xyz/utils": "4.0.0-alpha.1", + "@hyperlane-xyz/core": "4.0.0-alpha.2", + "@hyperlane-xyz/utils": "4.0.0-alpha.2", "@safe-global/api-kit": "1.3.0", "@safe-global/protocol-kit": "1.3.0", "@solana/spl-token": "^0.3.8", diff --git a/typescript/utils/CHANGELOG.md b/typescript/utils/CHANGELOG.md index df1a73e1f..887e7681d 100644 --- a/typescript/utils/CHANGELOG.md +++ b/typescript/utils/CHANGELOG.md @@ -1,6 +1,6 @@ # @hyperlane-xyz/utils -## 4.0.0-alpha.1 +## 4.0.0-alpha.2 ## 4.0.0-alpha.0 diff --git a/typescript/utils/package.json b/typescript/utils/package.json index 5b4235e32..d4e46ddd3 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": "4.0.0-alpha.1", + "version": "4.0.0-alpha.2", "dependencies": { "@cosmjs/encoding": "^0.31.3", "@solana/web3.js": "^1.78.0", diff --git a/yarn.lock b/yarn.lock index 6941f708c..2dfb37d5f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5691,8 +5691,8 @@ __metadata: "@aws-sdk/client-kms": "npm:^3.577.0" "@aws-sdk/client-s3": "npm:^3.577.0" "@hyperlane-xyz/registry": "npm:1.3.0" - "@hyperlane-xyz/sdk": "npm:4.0.0-alpha.0" - "@hyperlane-xyz/utils": "npm:4.0.0-alpha.0" + "@hyperlane-xyz/sdk": "npm:4.0.0-alpha.2" + "@hyperlane-xyz/utils": "npm:4.0.0-alpha.2" "@inquirer/prompts": "npm:^3.0.0" "@types/mocha": "npm:^10.0.1" "@types/node": "npm:^18.14.5" @@ -5736,12 +5736,12 @@ __metadata: languageName: node linkType: hard -"@hyperlane-xyz/core@npm:4.0.0-alpha.0, @hyperlane-xyz/core@workspace:solidity": +"@hyperlane-xyz/core@npm:4.0.0-alpha.2, @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:4.0.0-alpha.0" + "@hyperlane-xyz/utils": "npm:4.0.0-alpha.2" "@layerzerolabs/lz-evm-oapp-v2": "npm:2.0.2" "@layerzerolabs/solidity-examples": "npm:^1.1.0" "@nomiclabs/hardhat-ethers": "npm:^2.2.3" @@ -5773,13 +5773,13 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/helloworld@npm:4.0.0-alpha.0, @hyperlane-xyz/helloworld@workspace:typescript/helloworld": +"@hyperlane-xyz/helloworld@npm:4.0.0-alpha.2, @hyperlane-xyz/helloworld@workspace:typescript/helloworld": version: 0.0.0-use.local resolution: "@hyperlane-xyz/helloworld@workspace:typescript/helloworld" dependencies: - "@hyperlane-xyz/core": "npm:4.0.0-alpha.0" + "@hyperlane-xyz/core": "npm:4.0.0-alpha.2" "@hyperlane-xyz/registry": "npm:1.3.0" - "@hyperlane-xyz/sdk": "npm:4.0.0-alpha.0" + "@hyperlane-xyz/sdk": "npm:4.0.0-alpha.2" "@nomiclabs/hardhat-ethers": "npm:^2.2.3" "@nomiclabs/hardhat-waffle": "npm:^2.0.6" "@openzeppelin/contracts-upgradeable": "npm:^4.9.3" @@ -5824,10 +5824,10 @@ __metadata: "@ethersproject/experimental": "npm:^5.7.0" "@ethersproject/hardware-wallets": "npm:^5.7.0" "@ethersproject/providers": "npm:^5.7.2" - "@hyperlane-xyz/helloworld": "npm:4.0.0-alpha.0" + "@hyperlane-xyz/helloworld": "npm:4.0.0-alpha.2" "@hyperlane-xyz/registry": "npm:1.3.0" - "@hyperlane-xyz/sdk": "npm:4.0.0-alpha.0" - "@hyperlane-xyz/utils": "npm:4.0.0-alpha.0" + "@hyperlane-xyz/sdk": "npm:4.0.0-alpha.2" + "@hyperlane-xyz/utils": "npm:4.0.0-alpha.2" "@nomiclabs/hardhat-ethers": "npm:^2.2.3" "@nomiclabs/hardhat-etherscan": "npm:^3.0.3" "@nomiclabs/hardhat-waffle": "npm:^2.0.6" @@ -5915,15 +5915,15 @@ __metadata: languageName: node linkType: hard -"@hyperlane-xyz/sdk@npm:4.0.0-alpha.0, @hyperlane-xyz/sdk@workspace:typescript/sdk": +"@hyperlane-xyz/sdk@npm:4.0.0-alpha.2, @hyperlane-xyz/sdk@workspace:typescript/sdk": version: 0.0.0-use.local resolution: "@hyperlane-xyz/sdk@workspace:typescript/sdk" dependencies: "@aws-sdk/client-s3": "npm:^3.74.0" "@cosmjs/cosmwasm-stargate": "npm:^0.31.3" "@cosmjs/stargate": "npm:^0.31.3" - "@hyperlane-xyz/core": "npm:4.0.0-alpha.0" - "@hyperlane-xyz/utils": "npm:4.0.0-alpha.0" + "@hyperlane-xyz/core": "npm:4.0.0-alpha.2" + "@hyperlane-xyz/utils": "npm:4.0.0-alpha.2" "@nomiclabs/hardhat-ethers": "npm:^2.2.3" "@nomiclabs/hardhat-waffle": "npm:^2.0.6" "@safe-global/api-kit": "npm:1.3.0" @@ -5975,7 +5975,7 @@ __metadata: languageName: node linkType: hard -"@hyperlane-xyz/utils@npm:4.0.0-alpha.0, @hyperlane-xyz/utils@workspace:typescript/utils": +"@hyperlane-xyz/utils@npm:4.0.0-alpha.2, @hyperlane-xyz/utils@workspace:typescript/utils": version: 0.0.0-use.local resolution: "@hyperlane-xyz/utils@workspace:typescript/utils" dependencies: From 35f8699505535bc78d1e093b5a546f36efbbba68 Mon Sep 17 00:00:00 2001 From: Mohammed Hussan Date: Thu, 13 Jun 2024 12:34:59 +0100 Subject: [PATCH 37/73] feat(cli): Support creating agent configs from CLI (#3938) ### Description - Add support for creating agent configs using the CLI - registry agent-config command with a required --chains option - This will pick up local registry data Example usage: `hyperlane registry agent-config --chains anvil8545` ### Drive-by changes ### Related issues - Fixes #[3720](https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/3720) ### Backward compatibility Yes ### Testing Manual --- .changeset/lovely-boxes-bow.md | 5 ++ typescript/cli/package.json | 3 +- typescript/cli/src/commands/options.ts | 7 +++ typescript/cli/src/commands/registry.ts | 60 ++++++++++++++++++-- typescript/cli/src/commands/types.ts | 2 + typescript/cli/src/config/agent.ts | 75 +++++++++++++++++++++++++ yarn.lock | 10 ++++ 7 files changed, 157 insertions(+), 5 deletions(-) create mode 100644 .changeset/lovely-boxes-bow.md create mode 100644 typescript/cli/src/commands/types.ts create mode 100644 typescript/cli/src/config/agent.ts diff --git a/.changeset/lovely-boxes-bow.md b/.changeset/lovely-boxes-bow.md new file mode 100644 index 000000000..38ee46ae5 --- /dev/null +++ b/.changeset/lovely-boxes-bow.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/cli': minor +--- + +Add command to support creating agent configs diff --git a/typescript/cli/package.json b/typescript/cli/package.json index ed84f1155..68dbb24a8 100644 --- a/typescript/cli/package.json +++ b/typescript/cli/package.json @@ -18,7 +18,8 @@ "tsx": "^4.7.1", "yaml": "^2.4.1", "yargs": "^17.7.2", - "zod": "^3.21.2" + "zod": "^3.21.2", + "zod-validation-error": "^3.3.0" }, "devDependencies": { "@types/mocha": "^10.0.1", diff --git a/typescript/cli/src/commands/options.ts b/typescript/cli/src/commands/options.ts index 878a58585..3726ecb71 100644 --- a/typescript/cli/src/commands/options.ts +++ b/typescript/cli/src/commands/options.ts @@ -106,6 +106,13 @@ export const agentConfigCommandOption = ( default: defaultPath, }); +export const chainTargetsCommandOption: Options = { + type: 'string', + description: 'Comma-separated list of chain names', + alias: 'c', + demandOption: true, +}; + export const outputFileCommandOption = ( defaultPath?: string, demandOption = false, diff --git a/typescript/cli/src/commands/registry.ts b/typescript/cli/src/commands/registry.ts index e6847a3a3..d2679f496 100644 --- a/typescript/cli/src/commands/registry.ts +++ b/typescript/cli/src/commands/registry.ts @@ -1,10 +1,14 @@ import { CommandModule } from 'yargs'; -import { CommandModuleWithContext } from '../context/types.js'; -import { log, logBlue, logGray, logTable } from '../logger.js'; +import { createAgentConfig } from '../config/agent.js'; +import { CommandContext, CommandModuleWithContext } from '../context/types.js'; +import { log, logBlue, logGray, logRed, logTable } from '../logger.js'; -const ChainTypes = ['mainnet', 'testnet']; -type ChainType = (typeof ChainTypes)[number]; +import { + chainTargetsCommandOption, + outputFileCommandOption, +} from './options.js'; +import { ChainType, ChainTypes } from './types.js'; /** * Parent command @@ -16,6 +20,7 @@ export const registryCommand: CommandModule = { yargs .command(listCommand) .command(addressesCommand) + .command(createAgentConfigCommand) .version(false) .demandCommand(), handler: () => log('Command required'), @@ -88,3 +93,50 @@ const addressesCommand: CommandModuleWithContext<{ name: string }> = { } }, }; + +/** + * agent-config command + */ +const createAgentConfigCommand: CommandModuleWithContext<{ + chains: string; + out: string; +}> = { + command: 'agent-config', + describe: 'Create a new agent config', + + builder: { + chains: chainTargetsCommandOption, + out: outputFileCommandOption( + './configs/agent-config.json', + false, + 'The path to output an agent config JSON file.', + ), + }, + handler: async ({ + context, + chains, + out, + }: { + context: CommandContext; + chains: string; + out: string; + }) => { + const { multiProvider } = context; + + const chainNames = chains.split(','); + const invalidChainNames = chainNames.filter( + (chainName) => !multiProvider.hasChain(chainName), + ); + if (invalidChainNames.length > 0) { + logRed( + `Invalid chain names: ${invalidChainNames + .join(', ') + .replace(/, $/, '')}`, + ); + process.exit(1); + } + + await createAgentConfig({ context, chains: chainNames, out }); + process.exit(0); + }, +}; diff --git a/typescript/cli/src/commands/types.ts b/typescript/cli/src/commands/types.ts new file mode 100644 index 000000000..bc017069a --- /dev/null +++ b/typescript/cli/src/commands/types.ts @@ -0,0 +1,2 @@ +export const ChainTypes = ['mainnet', 'testnet']; +export type ChainType = (typeof ChainTypes)[number]; diff --git a/typescript/cli/src/config/agent.ts b/typescript/cli/src/config/agent.ts new file mode 100644 index 000000000..a9d1be46f --- /dev/null +++ b/typescript/cli/src/config/agent.ts @@ -0,0 +1,75 @@ +import { fromError } from 'zod-validation-error'; + +import { + AgentConfigSchema, + ChainMap, + HyperlaneCore, + HyperlaneDeploymentArtifacts, + buildAgentConfig, +} from '@hyperlane-xyz/sdk'; +import { objMap, promiseObjAll } from '@hyperlane-xyz/utils'; + +import { CommandContext } from '../context/types.js'; +import { logBlue, logGreen, logRed } from '../logger.js'; +import { writeYamlOrJson } from '../utils/files.js'; + +export async function createAgentConfig({ + context, + chains, + out, +}: { + context: CommandContext; + chains: string[]; + out: string; +}) { + logBlue('\nCreating agent config...'); + + const { registry, multiProvider, chainMetadata } = context; + const addresses = await registry.getAddresses(); + + const core = HyperlaneCore.fromAddressesMap(addresses, multiProvider); + + const startBlocks = await promiseObjAll( + objMap(addresses, async (chain, _) => { + // If the index.from is specified in the chain metadata, use that. + const indexFrom = chainMetadata[chain].index?.from; + if (indexFrom !== undefined) { + return indexFrom; + } + + const mailbox = core.getContracts(chain).mailbox; + try { + const deployedBlock = await mailbox.deployedBlock(); + return deployedBlock.toNumber(); + } catch (err) { + logRed( + `Failed to get deployed block to set an index for ${chain}, this is potentially an issue with rpc provider or a misconfiguration`, + ); + process.exit(1); + } + }), + ); + + // @TODO: consider adding additional config used to pass in gas prices for Cosmos chains + const agentConfig = buildAgentConfig( + chains, + multiProvider, + addresses as ChainMap, + startBlocks, + ); + + try { + AgentConfigSchema.parse(agentConfig); + } catch (e) { + logRed( + `Agent config is invalid, this is possibly due to required contracts not being deployed. See details below:\n${fromError( + e, + ).toString()}`, + ); + process.exit(1); + } + + logBlue(`Agent config is valid, writing to file ${out}`); + writeYamlOrJson(out, agentConfig, 'json'); + logGreen(`✅ Agent config successfully written to ${out}`); +} diff --git a/yarn.lock b/yarn.lock index 2dfb37d5f..e8bf9eb8c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5715,6 +5715,7 @@ __metadata: yaml: "npm:^2.4.1" yargs: "npm:^17.7.2" zod: "npm:^3.21.2" + zod-validation-error: "npm:^3.3.0" bin: hyperlane: ./dist/cli.js languageName: unknown @@ -26173,6 +26174,15 @@ __metadata: languageName: node linkType: hard +"zod-validation-error@npm:^3.3.0": + version: 3.3.0 + resolution: "zod-validation-error@npm:3.3.0" + peerDependencies: + zod: ^3.18.0 + checksum: 19574cbc453c7a41105de572546e95191958f459dd93440f541a42c0ff209b56f1cd54e8f8ab1899430dd7c183e11cd16e8cace0bd4fc5d356ef772645210792 + languageName: node + linkType: hard + "zod@npm:^3.21.2": version: 3.21.2 resolution: "zod@npm:3.21.2" From 9304fe241e0344d8353556fd449974d60be7f865 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Noah=20Bayindirli=20=F0=9F=A5=82?= Date: Thu, 13 Jun 2024 14:00:04 -0400 Subject: [PATCH 38/73] feat(cli): support more ISM types in CLI self-relay (#3954) ### Description - adds self-relay support to CLI ### Drive-by changes - none ### Related issues - fixes https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/3955 ### Backward compatibility - yes ### Testing - manual --------- Signed-off-by: Paul Balaji Co-authored-by: Yorke Rhodes Co-authored-by: Paul Balaji --- .changeset/clean-bees-repeat.md | 6 ++ typescript/cli/src/send/message.ts | 2 +- typescript/cli/src/send/transfer.ts | 2 +- typescript/cli/src/status/message.ts | 2 +- typescript/sdk/src/core/HyperlaneCore.ts | 62 +++++++++++-------- .../src/ism/metadata/builder.hardhat-test.ts | 3 +- .../liquidity-layer.hardhat-test.ts | 1 + .../middleware/query/queries.hardhat-test.ts | 1 + 8 files changed, 48 insertions(+), 31 deletions(-) create mode 100644 .changeset/clean-bees-repeat.md diff --git a/.changeset/clean-bees-repeat.md b/.changeset/clean-bees-repeat.md new file mode 100644 index 000000000..0371000fa --- /dev/null +++ b/.changeset/clean-bees-repeat.md @@ -0,0 +1,6 @@ +--- +'@hyperlane-xyz/cli': minor +'@hyperlane-xyz/sdk': minor +--- + +Use metadata builders in message relaying diff --git a/typescript/cli/src/send/message.ts b/typescript/cli/src/send/message.ts index 286349587..729f3be04 100644 --- a/typescript/cli/src/send/message.ts +++ b/typescript/cli/src/send/message.ts @@ -107,7 +107,7 @@ async function executeDelivery({ if (selfRelay) { log('Attempting self-relay of message'); - await core.relayMessage(message); + await core.relayMessage(message, dispatchTx); logGreen('Message was self-relayed!'); } else { if (skipWaitForDelivery) { diff --git a/typescript/cli/src/send/transfer.ts b/typescript/cli/src/send/transfer.ts index 23cd5ba52..126ddf426 100644 --- a/typescript/cli/src/send/transfer.ts +++ b/typescript/cli/src/send/transfer.ts @@ -164,7 +164,7 @@ async function executeDelivery({ logBlue(`Message ID: ${message.id}`); if (selfRelay) { - await core.relayMessage(message); + await core.relayMessage(message, transferTxReceipt); logGreen('Message was self-relayed!'); return; } diff --git a/typescript/cli/src/status/message.ts b/typescript/cli/src/status/message.ts index 77118b6de..d7b36efd1 100644 --- a/typescript/cli/src/status/message.ts +++ b/typescript/cli/src/status/message.ts @@ -57,7 +57,7 @@ export async function checkMessageStatus({ const receipt = await core.getDispatchTx(origin, messageId); const messages = core.getDispatchedMessages(receipt); - await core.relayMessage(messages[0]); + await core.relayMessage(messages[0], receipt); logGreen(`Message ${messageId} was self-relayed!`); } } diff --git a/typescript/sdk/src/core/HyperlaneCore.ts b/typescript/sdk/src/core/HyperlaneCore.ts index 12d20c414..fe4fd7d68 100644 --- a/typescript/sdk/src/core/HyperlaneCore.ts +++ b/typescript/sdk/src/core/HyperlaneCore.ts @@ -9,7 +9,6 @@ import { ProtocolType, addressToBytes32, bytes32ToAddress, - eqAddress, messageId, objFilter, objMap, @@ -21,8 +20,9 @@ import { HyperlaneApp } from '../app/HyperlaneApp.js'; import { appFromAddressesMapHelper } from '../contracts/contracts.js'; import { HyperlaneAddressesMap } from '../contracts/types.js'; import { OwnableConfig } from '../deploy/types.js'; +import { DerivedHookConfig, EvmHookReader } from '../hook/EvmHookReader.js'; import { DerivedIsmConfig, EvmIsmReader } from '../ism/EvmIsmReader.js'; -import { IsmType, ModuleType, ismTypeToModuleType } from '../ism/types.js'; +import { BaseMetadataBuilder } from '../ism/metadata/builder.js'; import { MultiProvider } from '../providers/MultiProvider.js'; import { RouterConfig } from '../router/types.js'; import { ChainMap, ChainName } from '../types.js'; @@ -87,12 +87,22 @@ export class HyperlaneCore extends HyperlaneApp { return this.multiProvider.getChainName(message.parsed.destination); } - getRecipientIsmAddress(message: DispatchedMessage): Promise
{ + protected getOrigin(message: DispatchedMessage): ChainName { + return this.multiProvider.getChainName(message.parsed.origin); + } + + async getRecipientIsmAddress(message: DispatchedMessage): Promise
{ const destinationMailbox = this.contractsMap[this.getDestination(message)]; const ethAddress = bytes32ToAddress(message.parsed.recipient); return destinationMailbox.mailbox.recipientIsm(ethAddress); } + async getHookAddress(message: DispatchedMessage): Promise
{ + const destinationMailbox = this.contractsMap[this.getOrigin(message)]; + /* TODO: requiredHook() account for here: https://github.com/hyperlane-xyz/hyperlane-monorepo/pull/3693 */ + return destinationMailbox.mailbox.defaultHook(); + } + async getRecipientIsmConfig( message: DispatchedMessage, ): Promise { @@ -102,31 +112,28 @@ export class HyperlaneCore extends HyperlaneApp { return ismReader.deriveIsmConfig(address); } - async buildMetadata(message: DispatchedMessage): Promise { + async getHookConfig(message: DispatchedMessage): Promise { + const originChain = this.getOrigin(message); + const hookReader = new EvmHookReader(this.multiProvider, originChain); + const address = await this.getHookAddress(message); + return hookReader.deriveHookConfig(address); + } + + async buildMetadata( + message: DispatchedMessage, + dispatchTx: TransactionReceipt, + ): Promise { const ismConfig = await this.getRecipientIsmConfig(message); - const destinationChain = this.getDestination(message); + const hookConfig = await this.getHookConfig(message); - switch (ismConfig.type) { - case IsmType.TRUSTED_RELAYER: - // eslint-disable-next-line no-case-declarations - const destinationSigner = await this.multiProvider.getSignerAddress( - destinationChain, - ); - if (!eqAddress(destinationSigner, ismConfig.relayer)) { - this.logger.warn( - `${destinationChain} signer ${destinationSigner} does not match trusted relayer ${ismConfig.relayer}`, - ); - } - } + const baseMetadataBuilder = new BaseMetadataBuilder(this); - // TODO: implement metadata builders for other module types - const moduleType = ismTypeToModuleType(ismConfig.type); - switch (moduleType) { - case ModuleType.NULL: - return '0x'; - default: - throw new Error(`Unsupported module type ${moduleType}`); - } + return baseMetadataBuilder.build({ + ism: ismConfig, + hook: hookConfig, + message, + dispatchTx, + }); } async sendMessage( @@ -167,8 +174,9 @@ export class HyperlaneCore extends HyperlaneApp { async relayMessage( message: DispatchedMessage, + dispatchTx: ethers.ContractReceipt, ): Promise { - const metadata = await this.buildMetadata(message); + const metadata = await this.buildMetadata(message, dispatchTx); const destinationChain = this.getDestination(message); const mailbox = this.contractsMap[destinationChain].mailbox; @@ -269,7 +277,7 @@ export class HyperlaneCore extends HyperlaneApp { async getDispatchTx( originChain: ChainName, messageId: string, - ): Promise { + ): Promise { const mailbox = this.contractsMap[originChain].mailbox; const filter = mailbox.filters.DispatchId(messageId); const matchingEvents = await mailbox.queryFilter(filter); diff --git a/typescript/sdk/src/ism/metadata/builder.hardhat-test.ts b/typescript/sdk/src/ism/metadata/builder.hardhat-test.ts index 844a87df5..09f90012c 100644 --- a/typescript/sdk/src/ism/metadata/builder.hardhat-test.ts +++ b/typescript/sdk/src/ism/metadata/builder.hardhat-test.ts @@ -114,7 +114,8 @@ describe('BaseMetadataBuilder', () => { ); }); - describe('#build', () => { + // eslint-disable-next-line jest/no-disabled-tests + describe.skip('#build', () => { let origin: ChainName; let destination: ChainName; let context: MetadataContext; diff --git a/typescript/sdk/src/middleware/liquidity-layer/liquidity-layer.hardhat-test.ts b/typescript/sdk/src/middleware/liquidity-layer/liquidity-layer.hardhat-test.ts index 51732aaff..cf7bdde70 100644 --- a/typescript/sdk/src/middleware/liquidity-layer/liquidity-layer.hardhat-test.ts +++ b/typescript/sdk/src/middleware/liquidity-layer/liquidity-layer.hardhat-test.ts @@ -33,6 +33,7 @@ import { PortalAdapterConfig, } from './LiquidityLayerRouterDeployer.js'; +// eslint-disable-next-line jest/no-disabled-tests describe.skip('LiquidityLayerRouter', async () => { const localChain = TestChainName.test1; const remoteChain = TestChainName.test2; diff --git a/typescript/sdk/src/middleware/query/queries.hardhat-test.ts b/typescript/sdk/src/middleware/query/queries.hardhat-test.ts index 00ab9b855..1a507f77c 100644 --- a/typescript/sdk/src/middleware/query/queries.hardhat-test.ts +++ b/typescript/sdk/src/middleware/query/queries.hardhat-test.ts @@ -24,6 +24,7 @@ import { InterchainQueryChecker } from './InterchainQueryChecker.js'; import { InterchainQueryDeployer } from './InterchainQueryDeployer.js'; import { InterchainQueryFactories } from './contracts.js'; +// eslint-disable-next-line jest/no-disabled-tests describe.skip('InterchainQueryRouter', async () => { const localChain = TestChainName.test1; const remoteChain = TestChainName.test2; From 129bd871de301e47d8ba5a76c0a65631b940832e Mon Sep 17 00:00:00 2001 From: Lee <6251863+ltyu@users.noreply.github.com> Date: Sun, 16 Jun 2024 18:07:32 -0400 Subject: [PATCH 39/73] feat: `hyperlane config create chain` prompts for `displayName` (#3969) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Description - Add logic to `hyperlane config create chain` to prompt for `displayName` ![Screenshot 2024-06-14 at 4 33 26 PM](https://github.com/hyperlane-xyz/hyperlane-monorepo/assets/6251863/36e00fc4-f41a-4069-911f-4966bbb6f6e6) ### Backward compatibility Yes ### Testing Manual --- .changeset/new-timers-applaud.md | 5 +++++ typescript/cli/src/config/chain.ts | 6 ++++++ 2 files changed, 11 insertions(+) create mode 100644 .changeset/new-timers-applaud.md diff --git a/.changeset/new-timers-applaud.md b/.changeset/new-timers-applaud.md new file mode 100644 index 000000000..af8820eac --- /dev/null +++ b/.changeset/new-timers-applaud.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/cli': minor +--- + +Add chain displayName prompt with default diff --git a/typescript/cli/src/config/chain.ts b/typescript/cli/src/config/chain.ts index dbda2eb9f..bcedd179c 100644 --- a/typescript/cli/src/config/chain.ts +++ b/typescript/cli/src/config/chain.ts @@ -61,6 +61,11 @@ export async function createChainConfig({ validate: (chainName) => ZChainName.safeParse(chainName).success, }); + const displayName = await input({ + message: 'Enter chain display name', + default: name[0].toUpperCase() + name.slice(1), + }); + const chainId = parseInt( await detectAndConfirmOrPrompt( async () => { @@ -76,6 +81,7 @@ export async function createChainConfig({ const metadata: ChainMetadata = { name, + displayName, chainId, domainId: chainId, protocol: ProtocolType.Ethereum, From ec5d43cc4cd2b6b00c8ed3836a02893c669f905e Mon Sep 17 00:00:00 2001 From: Connor McEwen Date: Mon, 17 Jun 2024 13:12:28 -0400 Subject: [PATCH 40/73] chore: merge main to cli-2.0 (#3964) --- rust/config/mainnet_config.json | 52 +- rust/config/testnet_config.json | 50 + solidity/lib/forge-std | 2 +- typescript/helloworld/CHANGELOG.md | 253 --- typescript/helloworld/package.json | 1 + typescript/infra/CHANGELOG.md | 41 +- .../config/environments/mainnet3/agent.ts | 6 +- .../config/environments/mainnet3/chains.ts | 28 +- .../config/environments/mainnet3/funding.ts | 5 +- .../environments/mainnet3/helloworld.ts | 4 - .../config/environments/mainnet3/index.ts | 46 +- .../environments/mainnet3/liquidityLayer.ts | 2 - .../infra/config/environments/test/index.ts | 9 +- .../config/environments/testnet4/agent.ts | 5 +- .../testnet4/aw-validators/hyperlane.json | 3 + .../config/environments/testnet4/chains.ts | 15 +- .../testnet4/core/verification.json | 290 +++ .../config/environments/testnet4/funding.ts | 7 +- .../environments/testnet4/gas-oracle.ts | 2 + .../environments/testnet4/helloworld.ts | 8 +- .../config/environments/testnet4/index.ts | 33 +- .../testnet4/ism/verification.json | 1560 +++++++++-------- .../environments/testnet4/middleware.ts | 3 - .../testnet4/supportedChainNames.ts | 1 + .../environments/testnet4/validators.ts | 15 + typescript/infra/config/registry.ts | 29 +- .../helloworld-kathy/templates/_helpers.tpl | 4 - .../templates/external-secret.yaml | 4 - .../helm/key-funder/templates/cron-job.yaml | 4 - .../templates/env-var-external-secret.yaml | 4 - .../templates/circle-relayer-deployment.yaml | 4 - .../templates/env-var-external-secret.yaml | 4 - .../templates/portal-relayer-deployment.yaml | 4 - typescript/infra/package.json | 1 + typescript/infra/scripts/agent-utils.ts | 88 +- .../scripts/agents/update-agent-config.ts | 7 +- typescript/infra/scripts/check-rpc-urls.ts | 11 +- typescript/infra/scripts/deploy.ts | 10 +- .../funding/fund-keys-from-deployer.ts | 11 +- typescript/infra/scripts/helloworld/kathy.ts | 25 +- typescript/infra/scripts/helloworld/utils.ts | 5 - .../infra/scripts/print-chain-metadatas.ts | 12 +- typescript/infra/scripts/print-gas-prices.ts | 88 +- .../infra/scripts/print-token-prices.ts | 12 +- typescript/infra/scripts/verify.ts | 8 +- typescript/infra/src/agents/index.ts | 51 +- typescript/infra/src/config/chain.ts | 104 +- typescript/infra/src/config/environment.ts | 10 +- typescript/infra/src/config/funding.ts | 3 +- .../infra/src/config/helloworld/types.ts | 3 +- typescript/infra/src/config/middleware.ts | 3 - typescript/infra/src/funding/key-funder.ts | 1 - typescript/infra/src/helloworld/kathy.ts | 1 - .../src/middleware/liquidity-layer-relayer.ts | 1 - typescript/infra/src/utils/gcloud.ts | 141 +- typescript/infra/src/utils/utils.ts | 4 + typescript/sdk/CHANGELOG.md | 44 +- typescript/sdk/src/consts/multisigIsm.ts | 5 + typescript/sdk/src/metadata/agentConfig.ts | 10 + typescript/utils/CHANGELOG.md | 14 +- yarn.lock | 397 ++++- 61 files changed, 2146 insertions(+), 1422 deletions(-) diff --git a/rust/config/mainnet_config.json b/rust/config/mainnet_config.json index 25bfbb895..8b56ae474 100644 --- a/rust/config/mainnet_config.json +++ b/rust/config/mainnet_config.json @@ -634,13 +634,18 @@ }, "injective": { "bech32Prefix": "inj", + "blockExplorers": [], "blocks": { + "confirmations": 1, + "estimateBlockTime": 1, "reorgPeriod": 10 }, "canonicalAsset": "inj", "chainId": "injective-1", "contractAddressBytes": 20, - "domainId": "6909546", + "displayName": "Injective", + "domainId": 6909546, + "gasCurrencyCoinGeckoId": "injective-protocol", "gasPrice": { "amount": "700000000", "denom": "inj" @@ -658,12 +663,24 @@ "mailbox": "0x0f7fb53961d70687e352aa55cb329ca76edc0c19", "merkleTreeHook": "0x568ad3638447f07def384969f4ea39fae3802962", "name": "injective", + "nativeToken": { + "decimals": 18, + "denom": "inj", + "name": "Injective", + "symbol": "INJ" + }, "protocol": "cosmos", + "restUrls": [ + { + "http": "https://sentry.lcd.injective.network:443" + } + ], "rpcUrls": [ { - "http": "https://injective-rpc.polkachu.com" + "http": "https://sentry.tm.injective.network:443" } ], + "slip44": 118, "validatorAnnounce": "0x1fb225b2fcfbe75e614a1d627de97ff372242eed" }, "mantapacific": { @@ -837,13 +854,25 @@ }, "neutron": { "bech32Prefix": "neutron", + "blockExplorers": [ + { + "apiUrl": "https://www.mintscan.io/neutron", + "family": "other", + "name": "Mintscan", + "url": "https://www.mintscan.io/neutron" + } + ], "blocks": { + "confirmations": 1, + "estimateBlockTime": 3, "reorgPeriod": 1 }, "canonicalAsset": "untrn", "chainId": "neutron-1", "contractAddressBytes": 32, - "domainId": "1853125230", + "displayName": "Neutron", + "domainId": 1853125230, + "gasCurrencyCoinGeckoId": "neutron-3", "gasPrice": { "amount": "0.0053", "denom": "untrn" @@ -858,10 +887,22 @@ "from": 4000000 }, "interchainGasPaymaster": "0x504ee9ac43ec5814e00c7d21869a90ec52becb489636bdf893b7df9d606b5d67", + "isTestnet": false, "mailbox": "0x848426d50eb2104d5c6381ec63757930b1c14659c40db8b8081e516e7c5238fc", "merkleTreeHook": "0xcd30a0001cc1f436c41ef764a712ebabc5a144140e3fd03eafe64a9a24e4e27c", "name": "neutron", + "nativeToken": { + "decimals": 6, + "denom": "untrn", + "name": "Neutron", + "symbol": "NTRN" + }, "protocol": "cosmos", + "restUrls": [ + { + "http": "https://rest-lb.neutron.org" + } + ], "rpcUrls": [ { "http": "https://rpc-kralum.neutron-1.neutron.org" @@ -872,6 +913,7 @@ "prefix": "neutron", "type": "cosmosKey" }, + "slip44": 118, "validatorAnnounce": "0xf3aa0d652226e21ae35cd9035c492ae41725edc9036edf0d6a48701b153b90a0" }, "optimism": { @@ -998,8 +1040,8 @@ "name": "polygon", "nativeToken": { "decimals": 18, - "name": "Ether", - "symbol": "ETH" + "name": "Matic", + "symbol": "MATIC" }, "pausableHook": "0x748040afB89B8FdBb992799808215419d36A0930", "pausableIsm": "0x6741e91fFDC31c7786E3684427c628dad06299B0", diff --git a/rust/config/testnet_config.json b/rust/config/testnet_config.json index 31a60fecf..3680b07c9 100644 --- a/rust/config/testnet_config.json +++ b/rust/config/testnet_config.json @@ -207,6 +207,56 @@ "timelockController": "0x0000000000000000000000000000000000000000", "validatorAnnounce": "0x4f7179A691F8a684f56cF7Fed65171877d30739a" }, + "holesky": { + "blockExplorers": [ + { + "apiUrl": "https://api-holesky.etherscan.io/api", + "family": "etherscan", + "name": "Etherscan", + "url": "https://holesky.etherscan.io" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 13, + "reorgPeriod": 2 + }, + "chainId": 17000, + "displayName": "Holesky", + "domainId": 17000, + "domainRoutingIsmFactory": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD", + "fallbackRoutingHook": "0x07009DA2249c388aD0f416a235AfE90D784e1aAc", + "index": { + "from": 1543015 + }, + "interchainGasPaymaster": "0x5CBf4e70448Ed46c2616b04e9ebc72D29FF0cfA9", + "interchainSecurityModule": "0x751f2b684EeBb916dB777767CCb8fd793C8b2956", + "isTestnet": true, + "mailbox": "0x46f7C5D896bbeC89bE1B19e4485e59b4Be49e9Cc", + "merkleTreeHook": "0x98AAE089CaD930C64a76dD2247a2aC5773a4B8cE", + "name": "holesky", + "nativeToken": { + "decimals": 18, + "name": "Ether", + "symbol": "ETH" + }, + "pausableHook": "0xF7561c34f17A32D5620583A3397C304e7038a7F6", + "protocol": "ethereum", + "protocolFee": "0x6b1bb4ce664Bb4164AEB4d3D2E7DE7450DD8084C", + "proxyAdmin": "0x33dB966328Ea213b0f76eF96CA368AB37779F065", + "rpcUrls": [ + { + "http": "https://ethereum-holesky-rpc.publicnode.com" + } + ], + "staticAggregationHookFactory": "0x589C201a07c26b4725A4A829d772f24423da480B", + "staticAggregationIsmFactory": "0x54148470292C24345fb828B003461a9444414517", + "staticMerkleRootMultisigIsmFactory": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", + "staticMessageIdMultisigIsmFactory": "0x6966b0E55883d49BFB24539356a2f8A673E02039", + "storageGasOracle": "0x2b2a158B4059C840c7aC67399B153bb567D06303", + "testRecipient": "0x86fb9F1c124fB20ff130C41a79a432F770f67AFD", + "validatorAnnounce": "0xAb9B273366D794B7F80B4378bc8Aaca75C6178E2" + }, "plumetestnet": { "aggregationHook": "0x31dF0EEE7Dc7565665468698a0da221225619a1B", "blockExplorers": [ diff --git a/solidity/lib/forge-std b/solidity/lib/forge-std index e8a047e3f..52715a217 160000 --- a/solidity/lib/forge-std +++ b/solidity/lib/forge-std @@ -1 +1 @@ -Subproject commit e8a047e3f40f13fa37af6fe14e6e06283d9a060e +Subproject commit 52715a217dc51d0de15877878ab8213f6cbbbab5 diff --git a/typescript/helloworld/CHANGELOG.md b/typescript/helloworld/CHANGELOG.md index 47f1f8be2..e69de29bb 100644 --- a/typescript/helloworld/CHANGELOG.md +++ b/typescript/helloworld/CHANGELOG.md @@ -1,253 +0,0 @@ -# @hyperlane-xyz/helloworld - -## 4.0.0-alpha.2 - -### Patch Changes - -- Updated dependencies [6b63c5d82] - - @hyperlane-xyz/sdk@4.0.0-alpha.2 - - @hyperlane-xyz/core@4.0.0-alpha.2 - -## 4.0.0-alpha.0 - -### Patch Changes - -- Updated dependencies [bdcbe1d16] -- Updated dependencies [6db9fa9ad] - - @hyperlane-xyz/sdk@4.0.0-alpha.0 - - @hyperlane-xyz/core@4.0.0-alpha.0 - -## 4.0.0-alpha - -### Patch Changes - -- Updated dependencies [eb23e7729] -- Updated dependencies [1ec61debd] -- Updated dependencies [4663018fc] -- Updated dependencies [5e5886f2c] -- Updated dependencies [341b8affd] -- Updated dependencies [3dabcbdca] -- Updated dependencies [1d0d1bb36] -- Updated dependencies [74c879fa1] -- Updated dependencies [4bf7301ea] - - @hyperlane-xyz/sdk@4.0.0-alpha - - @hyperlane-xyz/core@4.0.0-alpha - -## 3.13.0 - -### Patch Changes - -- b6b26e2bb: fix: minor change was breaking in registry export -- Updated dependencies [39ea7cdef] -- Updated dependencies [babe816f8] -- Updated dependencies [b440d98be] -- Updated dependencies [0cf692e73] - - @hyperlane-xyz/sdk@3.13.0 - - @hyperlane-xyz/core@3.13.0 - -## 3.12.0 - -### Patch Changes - -- Updated dependencies [eba393680] -- Updated dependencies [69de68a66] - - @hyperlane-xyz/sdk@3.12.0 - - @hyperlane-xyz/core@3.12.0 - -## 3.11.1 - -### Patch Changes - -- Updated dependencies [c900da187] - - @hyperlane-xyz/sdk@3.11.1 - - @hyperlane-xyz/core@3.11.1 - -## 3.11.0 - -### Minor Changes - -- b63714ede: Convert all public hyperlane npm packages from CJS to pure ESM - -### Patch Changes - -- Updated dependencies [811ecfbba] -- Updated dependencies [f8b6ea467] -- Updated dependencies [d37cbab72] -- Updated dependencies [b6fdf2f7f] -- Updated dependencies [a86a8296b] -- Updated dependencies [2db77f177] -- Updated dependencies [3a08e31b6] -- Updated dependencies [917266dce] -- Updated dependencies [aab63d466] -- Updated dependencies [2e439423e] -- Updated dependencies [b63714ede] -- Updated dependencies [3528b281e] -- Updated dependencies [450e8e0d5] -- Updated dependencies [af2634207] - - @hyperlane-xyz/sdk@3.11.0 - - @hyperlane-xyz/core@3.11.0 - -## 3.10.0 - -### Minor Changes - -- 96485144a: SDK support for ICA deployment and operation. -- 4e7a43be6: Replace Debug logger with Pino - -### Patch Changes - -- Updated dependencies [96485144a] -- Updated dependencies [38358ecec] -- Updated dependencies [ed0d4188c] -- Updated dependencies [4e7a43be6] - - @hyperlane-xyz/sdk@3.10.0 - - @hyperlane-xyz/core@3.10.0 - -## 3.9.0 - -### Patch Changes - -- Updated dependencies [11f257ebc] - - @hyperlane-xyz/sdk@3.9.0 - - @hyperlane-xyz/core@3.9.0 - -## 3.8.2 - -### Patch Changes - -- @hyperlane-xyz/core@3.8.2 -- @hyperlane-xyz/sdk@3.8.2 - -## 3.8.1 - -### Patch Changes - -- Updated dependencies [5daaae274] - - @hyperlane-xyz/sdk@3.8.1 - - @hyperlane-xyz/core@3.8.1 - -## 3.8.0 - -### Minor Changes - -- 9681df08d: 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. - -### Patch Changes - -- Updated dependencies [9681df08d] -- Updated dependencies [9681df08d] -- Updated dependencies [9681df08d] -- Updated dependencies [9681df08d] -- Updated dependencies [9681df08d] -- Updated dependencies [9681df08d] -- Updated dependencies [9681df08d] -- Updated dependencies [9681df08d] -- Updated dependencies [9681df08d] -- Updated dependencies [9681df08d] -- Updated dependencies [9681df08d] -- Updated dependencies [9681df08d] - - @hyperlane-xyz/sdk@3.8.0 - - @hyperlane-xyz/core@3.8.0 - -## 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 - -- c9e0aedae: Improve client side StandardHookMetadata library interface -- Updated dependencies [c9e0aedae] - - @hyperlane-xyz/core@3.1.10 - - @hyperlane-xyz/sdk@3.1.10 diff --git a/typescript/helloworld/package.json b/typescript/helloworld/package.json index e10f34329..e35e45bd7 100644 --- a/typescript/helloworld/package.json +++ b/typescript/helloworld/package.json @@ -14,6 +14,7 @@ "@nomiclabs/hardhat-waffle": "^2.0.6", "@trivago/prettier-plugin-sort-imports": "^4.2.1", "@typechain/ethers-v5": "^11.1.2", + "@typechain/ethers-v6": "^0.5.1", "@typechain/hardhat": "^9.1.0", "@typescript-eslint/eslint-plugin": "^7.4.0", "@typescript-eslint/parser": "^7.4.0", diff --git a/typescript/infra/CHANGELOG.md b/typescript/infra/CHANGELOG.md index 6abcbec1b..e1da9e3b3 100644 --- a/typescript/infra/CHANGELOG.md +++ b/typescript/infra/CHANGELOG.md @@ -1,45 +1,12 @@ # @hyperlane-xyz/infra -## 4.0.0-alpha.2 +## 3.14.0 ### Patch Changes -- Updated dependencies [6b63c5d82] - - @hyperlane-xyz/sdk@4.0.0-alpha.2 - - @hyperlane-xyz/helloworld@4.0.0-alpha.2 - - @hyperlane-xyz/utils@4.0.0-alpha.2 - -## 4.0.0-alpha.0 - -### Patch Changes - -- Updated dependencies [bdcbe1d16] -- Updated dependencies [6db9fa9ad] - - @hyperlane-xyz/sdk@4.0.0-alpha.0 - - @hyperlane-xyz/helloworld@4.0.0-alpha.0 - - @hyperlane-xyz/utils@4.0.0-alpha.0 - -## 4.0.0-alpha - -### Minor Changes - -- 1ec61debd: Support hook config objects in warp config -- 341b8affd: Completes the EvmIsmModule for creating, reading and updating ISMs. - -### Patch Changes - -- Updated dependencies [eb23e7729] -- Updated dependencies [1ec61debd] -- Updated dependencies [4663018fc] -- Updated dependencies [5e5886f2c] -- Updated dependencies [341b8affd] -- Updated dependencies [3dabcbdca] -- Updated dependencies [1d0d1bb36] -- Updated dependencies [74c879fa1] -- Updated dependencies [4bf7301ea] - - @hyperlane-xyz/sdk@4.0.0-alpha - - @hyperlane-xyz/utils@4.0.0-alpha - - @hyperlane-xyz/helloworld@4.0.0-alpha +- @hyperlane-xyz/helloworld@3.14.0 +- @hyperlane-xyz/sdk@3.14.0 +- @hyperlane-xyz/utils@3.14.0 ## 3.13.0 diff --git a/typescript/infra/config/environments/mainnet3/agent.ts b/typescript/infra/config/environments/mainnet3/agent.ts index 971e30530..7d24fcc20 100644 --- a/typescript/infra/config/environments/mainnet3/agent.ts +++ b/typescript/infra/config/environments/mainnet3/agent.ts @@ -209,7 +209,7 @@ const hyperlane: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: 'd6bb976-20240520-164138', + tag: '939fa81-20240607-194607', }, gasPaymentEnforcement: gasPaymentEnforcement, metricAppContexts, @@ -226,7 +226,7 @@ const hyperlane: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: 'd6bb976-20240520-164138', + tag: '939fa81-20240607-194607', }, }, }; @@ -240,7 +240,7 @@ const releaseCandidate: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: 'c9c5d37-20240510-014327', + tag: '939fa81-20240607-194607', }, // We're temporarily (ab)using the RC relayer as a way to increase // message throughput. diff --git a/typescript/infra/config/environments/mainnet3/chains.ts b/typescript/infra/config/environments/mainnet3/chains.ts index 0e5d4144e..c511b72e6 100644 --- a/typescript/infra/config/environments/mainnet3/chains.ts +++ b/typescript/infra/config/environments/mainnet3/chains.ts @@ -1,30 +1,23 @@ import { ChainMap, ChainMetadata } from '@hyperlane-xyz/sdk'; -import { objKeys } from '@hyperlane-xyz/utils'; -import { getChainMetadatas } from '../../../src/config/chain.js'; -import { getChain } from '../../registry.js'; +import { isEthereumProtocolChain } from '../../../src/utils/utils.js'; import { supportedChainNames } from './supportedChainNames.js'; export const environment = 'mainnet3'; -const { - ethereumMetadatas: defaultEthereumMainnetConfigs, - nonEthereumMetadatas: nonEthereumMainnetConfigs, -} = getChainMetadatas(supportedChainNames); +export const ethereumChainNames = supportedChainNames.filter( + isEthereumProtocolChain, +); -export const ethereumMainnetConfigs: ChainMap = { - ...defaultEthereumMainnetConfigs, +export const chainMetadataOverrides: ChainMap> = { bsc: { - ...getChain('bsc'), transactionOverrides: { gasPrice: 3 * 10 ** 9, // 3 gwei }, }, polygon: { - ...getChain('polygon'), blocks: { - ...getChain('polygon').blocks, confirmations: 3, }, transactionOverrides: { @@ -35,9 +28,7 @@ export const ethereumMainnetConfigs: ChainMap = { }, }, ethereum: { - ...getChain('ethereum'), blocks: { - ...getChain('ethereum').blocks, confirmations: 3, }, transactionOverrides: { @@ -46,7 +37,6 @@ export const ethereumMainnetConfigs: ChainMap = { }, }, scroll: { - ...getChain('scroll'), transactionOverrides: { // Scroll doesn't use EIP 1559 and the gas price that's returned is sometimes // too low for the transaction to be included in a reasonable amount of time - @@ -55,17 +45,9 @@ export const ethereumMainnetConfigs: ChainMap = { }, }, moonbeam: { - ...getChain('moonbeam'), transactionOverrides: { maxFeePerGas: 350 * 10 ** 9, // 350 gwei maxPriorityFeePerGas: 50 * 10 ** 9, // 50 gwei }, }, }; - -export const mainnetConfigs: ChainMap = { - ...ethereumMainnetConfigs, - ...nonEthereumMainnetConfigs, -}; - -export const ethereumChainNames = objKeys(ethereumMainnetConfigs); diff --git a/typescript/infra/config/environments/mainnet3/funding.ts b/typescript/infra/config/environments/mainnet3/funding.ts index f9f15f0a8..25e01b03d 100644 --- a/typescript/infra/config/environments/mainnet3/funding.ts +++ b/typescript/infra/config/environments/mainnet3/funding.ts @@ -1,5 +1,3 @@ -import { RpcConsensusType } from '@hyperlane-xyz/sdk'; - import { KeyFunderConfig } from '../../../src/config/funding.js'; import { Role } from '../../../src/roles.js'; import { Contexts } from '../../contexts.js'; @@ -9,7 +7,7 @@ import { environment } from './chains.js'; export const keyFunderConfig: KeyFunderConfig = { docker: { repo: 'gcr.io/abacus-labs-dev/hyperlane-monorepo', - tag: 'b22a0f4-20240523-140812', + tag: '7720875-20240531-072251', }, // We're currently using the same deployer/key funder key as mainnet2. // To minimize nonce clobbering we offset the key funder cron @@ -23,7 +21,6 @@ export const keyFunderConfig: KeyFunderConfig = { [Contexts.Hyperlane]: [Role.Relayer, Role.Kathy], [Contexts.ReleaseCandidate]: [Role.Relayer, Role.Kathy], }, - connectionType: RpcConsensusType.Fallback, // desired balance config desiredBalancePerChain: { arbitrum: '0.5', diff --git a/typescript/infra/config/environments/mainnet3/helloworld.ts b/typescript/infra/config/environments/mainnet3/helloworld.ts index 4f15ba912..0df01f1d7 100644 --- a/typescript/infra/config/environments/mainnet3/helloworld.ts +++ b/typescript/infra/config/environments/mainnet3/helloworld.ts @@ -1,5 +1,3 @@ -import { RpcConsensusType } from '@hyperlane-xyz/sdk'; - import { HelloWorldConfig, HelloWorldKathyRunMode, @@ -26,7 +24,6 @@ export const hyperlane: HelloWorldConfig = { }, messageSendTimeout: 1000 * 60 * 8, // 8 min messageReceiptTimeout: 1000 * 60 * 20, // 20 min - connectionType: RpcConsensusType.Fallback, 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) }, }; @@ -46,7 +43,6 @@ export const releaseCandidate: HelloWorldConfig = { }, messageSendTimeout: 1000 * 60 * 8, // 8 min messageReceiptTimeout: 1000 * 60 * 20, // 20 min - connectionType: RpcConsensusType.Fallback, }, }; diff --git a/typescript/infra/config/environments/mainnet3/index.ts b/typescript/infra/config/environments/mainnet3/index.ts index a8bc14983..94c76e53a 100644 --- a/typescript/infra/config/environments/mainnet3/index.ts +++ b/typescript/infra/config/environments/mainnet3/index.ts @@ -1,16 +1,20 @@ -import { ChainMetadata, RpcConsensusType } from '@hyperlane-xyz/sdk'; -import { ProtocolType, objFilter } from '@hyperlane-xyz/utils'; +import { IRegistry } from '@hyperlane-xyz/registry'; import { getKeysForRole, + getMultiProtocolProvider, getMultiProviderForRole, } from '../../../scripts/agent-utils.js'; +import { getRegistryForEnvironment } from '../../../src/config/chain.js'; import { EnvironmentConfig } from '../../../src/config/environment.js'; import { Role } from '../../../src/roles.js'; import { Contexts } from '../../contexts.js'; import { agents } from './agent.js'; -import { environment as environmentName, mainnetConfigs } from './chains.js'; +import { + chainMetadataOverrides, + environment as environmentName, +} from './chains.js'; import { core } from './core.js'; import { keyFunderConfig } from './funding.js'; import { helloWorld } from './helloworld.js'; @@ -18,34 +22,38 @@ import { igp } from './igp.js'; import { infrastructure } from './infrastructure.js'; import { bridgeAdapterConfigs, relayerConfig } from './liquidityLayer.js'; import { owners } from './owners.js'; +import { supportedChainNames } from './supportedChainNames.js'; + +const getRegistry = async (useSecrets = true): Promise => + getRegistryForEnvironment( + environmentName, + supportedChainNames, + chainMetadataOverrides, + useSecrets, + ); export const environment: EnvironmentConfig = { environment: environmentName, - chainMetadataConfigs: mainnetConfigs, - getMultiProvider: ( + supportedChainNames, + getRegistry, + getMultiProtocolProvider: async () => + getMultiProtocolProvider(await getRegistry()), + getMultiProvider: async ( context: Contexts = Contexts.Hyperlane, role: Role = Role.Deployer, - connectionType?: RpcConsensusType, - ) => { - const config = objFilter( - mainnetConfigs, - (_, chainMetadata): chainMetadata is ChainMetadata => - chainMetadata.protocol === ProtocolType.Ethereum, - ); - - return getMultiProviderForRole( - config, + useSecrets?: boolean, + ) => + getMultiProviderForRole( environmentName, + await getRegistry(useSecrets), context, role, undefined, - connectionType, - ); - }, + ), getKeys: ( context: Contexts = Contexts.Hyperlane, role: Role = Role.Deployer, - ) => getKeysForRole(mainnetConfigs, environmentName, context, role), + ) => getKeysForRole(environmentName, supportedChainNames, context, role), agents, core, igp, diff --git a/typescript/infra/config/environments/mainnet3/liquidityLayer.ts b/typescript/infra/config/environments/mainnet3/liquidityLayer.ts index ac311f447..0f3a6d287 100644 --- a/typescript/infra/config/environments/mainnet3/liquidityLayer.ts +++ b/typescript/infra/config/environments/mainnet3/liquidityLayer.ts @@ -2,7 +2,6 @@ import { BridgeAdapterConfig, BridgeAdapterType, ChainMap, - RpcConsensusType, } from '@hyperlane-xyz/sdk'; import { LiquidityLayerRelayerConfig } from '../../../src/config/middleware.js'; @@ -50,5 +49,4 @@ export const relayerConfig: LiquidityLayerRelayerConfig = { namespace: environment, prometheusPushGateway: 'http://prometheus-prometheus-pushgateway.monitoring.svc.cluster.local:9091', - connectionType: RpcConsensusType.Single, }; diff --git a/typescript/infra/config/environments/test/index.ts b/typescript/infra/config/environments/test/index.ts index c21e411e5..cdde17fe3 100644 --- a/typescript/infra/config/environments/test/index.ts +++ b/typescript/infra/config/environments/test/index.ts @@ -5,6 +5,7 @@ import { MultiProvider, testChainMetadata } from '@hyperlane-xyz/sdk'; import { EnvironmentConfig } from '../../../src/config/environment.js'; import { agents } from './agent.js'; +import { testChainNames } from './chains.js'; import { core } from './core.js'; import { igp } from './igp.js'; import { infra } from './infra.js'; @@ -12,7 +13,13 @@ import { owners } from './owners.js'; export const environment: EnvironmentConfig = { environment: 'test', - chainMetadataConfigs: testChainMetadata, + supportedChainNames: testChainNames, + getRegistry: () => { + throw new Error('Not implemented'); + }, + getMultiProtocolProvider: () => { + throw new Error('Not implemented'); + }, agents, core, igp, diff --git a/typescript/infra/config/environments/testnet4/agent.ts b/typescript/infra/config/environments/testnet4/agent.ts index 62c162869..4ce2ab88d 100644 --- a/typescript/infra/config/environments/testnet4/agent.ts +++ b/typescript/infra/config/environments/testnet4/agent.ts @@ -36,6 +36,7 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig = { bsctestnet: true, eclipsetestnet: false, fuji: true, + holesky: true, plumetestnet: true, scrollsepolia: true, sepolia: true, @@ -46,6 +47,7 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig = { bsctestnet: true, eclipsetestnet: false, fuji: true, + holesky: true, plumetestnet: true, scrollsepolia: true, sepolia: true, @@ -57,6 +59,7 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig = { // Cannot scrape non-EVM chains eclipsetestnet: false, fuji: true, + holesky: true, plumetestnet: true, scrollsepolia: true, sepolia: true, @@ -124,7 +127,7 @@ const hyperlane: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: 'c9c5d37-20240510-014327', + tag: 'e09a360-20240520-090014', }, chains: validatorChainConfig(Contexts.Hyperlane), }, diff --git a/typescript/infra/config/environments/testnet4/aw-validators/hyperlane.json b/typescript/infra/config/environments/testnet4/aw-validators/hyperlane.json index 071d8e5cc..a78d0e953 100644 --- a/typescript/infra/config/environments/testnet4/aw-validators/hyperlane.json +++ b/typescript/infra/config/environments/testnet4/aw-validators/hyperlane.json @@ -20,6 +20,9 @@ "0x43e915573d9f1383cbf482049e4a012290759e7f" ] }, + "holesky": { + "validators": ["0x7ab28ad88bb45867137ea823af88e2cb02359c03"] + }, "plumetestnet": { "validators": [ "0xe765a214849f3ecdf00793b97d00422f2d408ea6", diff --git a/typescript/infra/config/environments/testnet4/chains.ts b/typescript/infra/config/environments/testnet4/chains.ts index 318d67e7b..caa3c1018 100644 --- a/typescript/infra/config/environments/testnet4/chains.ts +++ b/typescript/infra/config/environments/testnet4/chains.ts @@ -1,24 +1,19 @@ import { ChainMap, ChainMetadata } from '@hyperlane-xyz/sdk'; -import { objKeys } from '@hyperlane-xyz/utils'; -import { getChainMetadatas } from '../../../src/config/chain.js'; -import { getChain } from '../../registry.js'; +import { isEthereumProtocolChain } from '../../../src/utils/utils.js'; import { supportedChainNames } from './supportedChainNames.js'; export const environment = 'testnet4'; -const { ethereumMetadatas: defaultEthereumMainnetConfigs } = - getChainMetadatas(supportedChainNames); +export const ethereumChainNames = supportedChainNames.filter( + isEthereumProtocolChain, +); -export const testnetConfigs: ChainMap = { - ...defaultEthereumMainnetConfigs, +export const chainMetadataOverrides: ChainMap> = { bsctestnet: { - ...getChain('bsctestnet'), transactionOverrides: { gasPrice: 8 * 10 ** 9, // 8 gwei }, }, }; - -export const ethereumChainNames = objKeys(defaultEthereumMainnetConfigs); diff --git a/typescript/infra/config/environments/testnet4/core/verification.json b/typescript/infra/config/environments/testnet4/core/verification.json index 858d975ff..5abcc05a8 100644 --- a/typescript/infra/config/environments/testnet4/core/verification.json +++ b/typescript/infra/config/environments/testnet4/core/verification.json @@ -1841,6 +1841,296 @@ "name": "PausableHook" } ], + "holesky": [ + { + "address": "0x33dB966328Ea213b0f76eF96CA368AB37779F065", + "constructorArguments": "", + "isProxy": false, + "name": "ProxyAdmin" + }, + { + "address": "0xB08d78F439e55D02C398519eef61606A5926245F", + "constructorArguments": "0000000000000000000000000000000000000000000000000000000000004268", + "isProxy": false, + "name": "Mailbox" + }, + { + "address": "0x46f7C5D896bbeC89bE1B19e4485e59b4Be49e9Cc", + "constructorArguments": "000000000000000000000000b08d78f439e55d02c398519eef61606a5926245f00000000000000000000000033db966328ea213b0f76ef96ca368ab37779f06500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0x679Dc08cC3A4acFeea2f7CAFAa37561aE0b41Ce7", + "constructorArguments": "000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", + "isProxy": false, + "name": "PausableIsm" + }, + { + "address": "0x98AAE089CaD930C64a76dD2247a2aC5773a4B8cE", + "constructorArguments": "00000000000000000000000046f7c5d896bbec89be1b19e4485e59b4be49e9cc", + "isProxy": false, + "name": "MerkleTreeHook" + }, + { + "address": "0x07009DA2249c388aD0f416a235AfE90D784e1aAc", + "constructorArguments": "00000000000000000000000046f7c5d896bbec89be1b19e4485e59b4be49e9cc000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000098aae089cad930c64a76dd2247a2ac5773a4b8ce", + "isProxy": false, + "name": "FallbackRoutingHook" + }, + { + "address": "0xF7561c34f17A32D5620583A3397C304e7038a7F6", + "constructorArguments": "", + "isProxy": false, + "name": "PausableHook" + }, + { + "address": "0x2b2a158B4059C840c7aC67399B153bb567D06303", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" + }, + { + "address": "0x5CE550e14B82a9F32A0aaF9eFc4Fce548D8A0B3e", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" + }, + { + "address": "0x5CBf4e70448Ed46c2616b04e9ebc72D29FF0cfA9", + "constructorArguments": "0000000000000000000000005ce550e14b82a9f32a0aaf9efc4fce548d8a0b3e00000000000000000000000033db966328ea213b0f76ef96ca368ab37779f06500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0x2b2a158B4059C840c7aC67399B153bb567D06303", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" + }, + { + "address": "0x5CE550e14B82a9F32A0aaF9eFc4Fce548D8A0B3e", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" + }, + { + "address": "0x5CBf4e70448Ed46c2616b04e9ebc72D29FF0cfA9", + "constructorArguments": "0000000000000000000000005ce550e14b82a9f32a0aaf9efc4fce548d8a0b3e00000000000000000000000033db966328ea213b0f76ef96ca368ab37779f06500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0x2b2a158B4059C840c7aC67399B153bb567D06303", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" + }, + { + "address": "0x5CE550e14B82a9F32A0aaF9eFc4Fce548D8A0B3e", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" + }, + { + "address": "0x5CBf4e70448Ed46c2616b04e9ebc72D29FF0cfA9", + "constructorArguments": "0000000000000000000000005ce550e14b82a9f32a0aaf9efc4fce548d8a0b3e00000000000000000000000033db966328ea213b0f76ef96ca368ab37779f06500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0x2b2a158B4059C840c7aC67399B153bb567D06303", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" + }, + { + "address": "0x5CE550e14B82a9F32A0aaF9eFc4Fce548D8A0B3e", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" + }, + { + "address": "0x5CBf4e70448Ed46c2616b04e9ebc72D29FF0cfA9", + "constructorArguments": "0000000000000000000000005ce550e14b82a9f32a0aaf9efc4fce548d8a0b3e00000000000000000000000033db966328ea213b0f76ef96ca368ab37779f06500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0x2b2a158B4059C840c7aC67399B153bb567D06303", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" + }, + { + "address": "0x5CE550e14B82a9F32A0aaF9eFc4Fce548D8A0B3e", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" + }, + { + "address": "0x5CBf4e70448Ed46c2616b04e9ebc72D29FF0cfA9", + "constructorArguments": "0000000000000000000000005ce550e14b82a9f32a0aaf9efc4fce548d8a0b3e00000000000000000000000033db966328ea213b0f76ef96ca368ab37779f06500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0x2b2a158B4059C840c7aC67399B153bb567D06303", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" + }, + { + "address": "0x5CE550e14B82a9F32A0aaF9eFc4Fce548D8A0B3e", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" + }, + { + "address": "0x5CBf4e70448Ed46c2616b04e9ebc72D29FF0cfA9", + "constructorArguments": "0000000000000000000000005ce550e14b82a9f32a0aaf9efc4fce548d8a0b3e00000000000000000000000033db966328ea213b0f76ef96ca368ab37779f06500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0x98AAE089CaD930C64a76dD2247a2aC5773a4B8cE", + "constructorArguments": "00000000000000000000000046f7c5d896bbec89be1b19e4485e59b4be49e9cc", + "isProxy": false, + "name": "MerkleTreeHook" + }, + { + "address": "0x07009DA2249c388aD0f416a235AfE90D784e1aAc", + "constructorArguments": "00000000000000000000000046f7c5d896bbec89be1b19e4485e59b4be49e9cc000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000098aae089cad930c64a76dd2247a2ac5773a4b8ce", + "isProxy": false, + "name": "FallbackRoutingHook" + }, + { + "address": "0xF7561c34f17A32D5620583A3397C304e7038a7F6", + "constructorArguments": "", + "isProxy": false, + "name": "PausableHook" + }, + { + "address": "0x2b2a158B4059C840c7aC67399B153bb567D06303", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" + }, + { + "address": "0x5CE550e14B82a9F32A0aaF9eFc4Fce548D8A0B3e", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" + }, + { + "address": "0x5CBf4e70448Ed46c2616b04e9ebc72D29FF0cfA9", + "constructorArguments": "0000000000000000000000005ce550e14b82a9f32a0aaf9efc4fce548d8a0b3e00000000000000000000000033db966328ea213b0f76ef96ca368ab37779f06500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0x2b2a158B4059C840c7aC67399B153bb567D06303", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" + }, + { + "address": "0x5CE550e14B82a9F32A0aaF9eFc4Fce548D8A0B3e", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" + }, + { + "address": "0x5CBf4e70448Ed46c2616b04e9ebc72D29FF0cfA9", + "constructorArguments": "0000000000000000000000005ce550e14b82a9f32a0aaf9efc4fce548d8a0b3e00000000000000000000000033db966328ea213b0f76ef96ca368ab37779f06500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0x2b2a158B4059C840c7aC67399B153bb567D06303", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" + }, + { + "address": "0x5CE550e14B82a9F32A0aaF9eFc4Fce548D8A0B3e", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" + }, + { + "address": "0x5CBf4e70448Ed46c2616b04e9ebc72D29FF0cfA9", + "constructorArguments": "0000000000000000000000005ce550e14b82a9f32a0aaf9efc4fce548d8a0b3e00000000000000000000000033db966328ea213b0f76ef96ca368ab37779f06500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0x2b2a158B4059C840c7aC67399B153bb567D06303", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" + }, + { + "address": "0x5CE550e14B82a9F32A0aaF9eFc4Fce548D8A0B3e", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" + }, + { + "address": "0x5CBf4e70448Ed46c2616b04e9ebc72D29FF0cfA9", + "constructorArguments": "0000000000000000000000005ce550e14b82a9f32a0aaf9efc4fce548d8a0b3e00000000000000000000000033db966328ea213b0f76ef96ca368ab37779f06500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0x2b2a158B4059C840c7aC67399B153bb567D06303", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" + }, + { + "address": "0x5CE550e14B82a9F32A0aaF9eFc4Fce548D8A0B3e", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" + }, + { + "address": "0x5CBf4e70448Ed46c2616b04e9ebc72D29FF0cfA9", + "constructorArguments": "0000000000000000000000005ce550e14b82a9f32a0aaf9efc4fce548d8a0b3e00000000000000000000000033db966328ea213b0f76ef96ca368ab37779f06500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0x2b2a158B4059C840c7aC67399B153bb567D06303", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" + }, + { + "address": "0x5CE550e14B82a9F32A0aaF9eFc4Fce548D8A0B3e", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" + }, + { + "address": "0x5CBf4e70448Ed46c2616b04e9ebc72D29FF0cfA9", + "constructorArguments": "0000000000000000000000005ce550e14b82a9f32a0aaf9efc4fce548d8a0b3e00000000000000000000000033db966328ea213b0f76ef96ca368ab37779f06500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0x6b1bb4ce664Bb4164AEB4d3D2E7DE7450DD8084C", + "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", + "isProxy": false, + "name": "ProtocolFee" + }, + { + "address": "0xAb9B273366D794B7F80B4378bc8Aaca75C6178E2", + "constructorArguments": "00000000000000000000000046f7c5d896bbec89be1b19e4485e59b4be49e9cc", + "isProxy": false, + "name": "ValidatorAnnounce" + } + ], "moonbasealpha": [ { "address": "0xb241991527F1C21adE14F55589E5940aC4852Fa0", diff --git a/typescript/infra/config/environments/testnet4/funding.ts b/typescript/infra/config/environments/testnet4/funding.ts index 42d9c75c9..04ec56c98 100644 --- a/typescript/infra/config/environments/testnet4/funding.ts +++ b/typescript/infra/config/environments/testnet4/funding.ts @@ -1,5 +1,3 @@ -import { RpcConsensusType } from '@hyperlane-xyz/sdk'; - import { KeyFunderConfig } from '../../../src/config/funding.js'; import { Role } from '../../../src/roles.js'; import { Contexts } from '../../contexts.js'; @@ -9,7 +7,7 @@ import { environment } from './chains.js'; export const keyFunderConfig: KeyFunderConfig = { docker: { repo: 'gcr.io/abacus-labs-dev/hyperlane-monorepo', - tag: 'b22a0f4-20240523-140812', + tag: 'efa9025-20240605-091304', }, // We're currently using the same deployer key as testnet2. // To minimize nonce clobbering we offset the key funder cron @@ -23,13 +21,14 @@ export const keyFunderConfig: KeyFunderConfig = { [Contexts.Hyperlane]: [Role.Relayer, Role.Kathy], [Contexts.ReleaseCandidate]: [Role.Relayer, Role.Kathy], }, - connectionType: RpcConsensusType.Fallback, // desired balance config desiredBalancePerChain: { alfajores: '5', bsctestnet: '5', fuji: '5', plumetestnet: '0.2', + holesky: '5', + // Funder boosts itself upto 5x balance on L2 before dispersing funds scrollsepolia: '1', sepolia: '5', }, diff --git a/typescript/infra/config/environments/testnet4/gas-oracle.ts b/typescript/infra/config/environments/testnet4/gas-oracle.ts index 18d94c2e4..f16da2ed3 100644 --- a/typescript/infra/config/environments/testnet4/gas-oracle.ts +++ b/typescript/infra/config/environments/testnet4/gas-oracle.ts @@ -19,6 +19,7 @@ import { ethereumChainNames } from './chains.js'; const gasPrices: ChainMap = { alfajores: ethers.utils.parseUnits('10', 'gwei'), fuji: ethers.utils.parseUnits('30', 'gwei'), + holesky: ethers.utils.parseUnits('10', 'gwei'), bsctestnet: ethers.utils.parseUnits('15', 'gwei'), sepolia: ethers.utils.parseUnits('5', 'gwei'), scrollsepolia: ethers.utils.parseUnits('0.5', 'gwei'), @@ -48,6 +49,7 @@ const chainTokenRarity: ChainMap = { alfajores: Rarity.Common, fuji: Rarity.Rare, bsctestnet: Rarity.Rare, + holesky: Rarity.Common, sepolia: Rarity.Mythic, scrollsepolia: Rarity.Rare, chiado: Rarity.Common, diff --git a/typescript/infra/config/environments/testnet4/helloworld.ts b/typescript/infra/config/environments/testnet4/helloworld.ts index e6f094c7d..dee5ab3f2 100644 --- a/typescript/infra/config/environments/testnet4/helloworld.ts +++ b/typescript/infra/config/environments/testnet4/helloworld.ts @@ -1,5 +1,3 @@ -import { RpcConsensusType } from '@hyperlane-xyz/sdk'; - import { HelloWorldConfig, HelloWorldKathyRunMode, @@ -15,7 +13,7 @@ export const hyperlaneHelloworld: HelloWorldConfig = { kathy: { docker: { repo: 'gcr.io/abacus-labs-dev/hyperlane-monorepo', - tag: 'b22a0f4-20240523-140812', + tag: 'efa9025-20240605-091304', }, chainsToSkip: [], runEnv: environment, @@ -26,7 +24,6 @@ export const hyperlaneHelloworld: HelloWorldConfig = { }, messageSendTimeout: 1000 * 60 * 10, // 10 min messageReceiptTimeout: 1000 * 60 * 20, // 20 min - connectionType: RpcConsensusType.Fallback, }, }; @@ -35,7 +32,7 @@ export const releaseCandidateHelloworld: HelloWorldConfig = { kathy: { docker: { repo: 'gcr.io/abacus-labs-dev/hyperlane-monorepo', - tag: 'b22a0f4-20240523-140812', + tag: 'efa9025-20240605-091304', }, chainsToSkip: [], runEnv: environment, @@ -45,7 +42,6 @@ export const releaseCandidateHelloworld: HelloWorldConfig = { }, messageSendTimeout: 1000 * 60 * 8, // 8 min messageReceiptTimeout: 1000 * 60 * 20, // 20 min - connectionType: RpcConsensusType.Fallback, }, }; diff --git a/typescript/infra/config/environments/testnet4/index.ts b/typescript/infra/config/environments/testnet4/index.ts index 9eaa66e1c..1d1204083 100644 --- a/typescript/infra/config/environments/testnet4/index.ts +++ b/typescript/infra/config/environments/testnet4/index.ts @@ -1,15 +1,21 @@ -import { RpcConsensusType } from '@hyperlane-xyz/sdk'; +import { IRegistry } from '@hyperlane-xyz/registry'; +import { objMerge } from '@hyperlane-xyz/utils'; import { getKeysForRole, + getMultiProtocolProvider, getMultiProviderForRole, } from '../../../scripts/agent-utils.js'; +import { getRegistryForEnvironment } from '../../../src/config/chain.js'; import { EnvironmentConfig } from '../../../src/config/environment.js'; import { Role } from '../../../src/roles.js'; import { Contexts } from '../../contexts.js'; import { agents } from './agent.js'; -import { environment as environmentName, testnetConfigs } from './chains.js'; +import { + chainMetadataOverrides, + environment as environmentName, +} from './chains.js'; import { core } from './core.js'; import { keyFunderConfig } from './funding.js'; import { helloWorld } from './helloworld.js'; @@ -18,27 +24,38 @@ import { infrastructure } from './infrastructure.js'; import { bridgeAdapterConfigs } from './liquidityLayer.js'; import { liquidityLayerRelayerConfig } from './middleware.js'; import { owners } from './owners.js'; +import { supportedChainNames } from './supportedChainNames.js'; + +const getRegistry = async (useSecrets = true): Promise => + getRegistryForEnvironment( + environmentName, + supportedChainNames, + chainMetadataOverrides, + useSecrets, + ); export const environment: EnvironmentConfig = { environment: environmentName, - chainMetadataConfigs: testnetConfigs, - getMultiProvider: ( + supportedChainNames, + getRegistry, + getMultiProtocolProvider: async () => + getMultiProtocolProvider(await getRegistry()), + getMultiProvider: async ( context: Contexts = Contexts.Hyperlane, role: Role = Role.Deployer, - connectionType?: RpcConsensusType, + useSecrets?: boolean, ) => getMultiProviderForRole( - testnetConfigs, environmentName, + await getRegistry(useSecrets), context, role, undefined, - connectionType, ), getKeys: ( context: Contexts = Contexts.Hyperlane, role: Role = Role.Deployer, - ) => getKeysForRole(testnetConfigs, environmentName, context, role), + ) => getKeysForRole(environmentName, supportedChainNames, context, role), agents, core, igp, diff --git a/typescript/infra/config/environments/testnet4/ism/verification.json b/typescript/infra/config/environments/testnet4/ism/verification.json index 10b6f54c1..2b2cd3331 100644 --- a/typescript/infra/config/environments/testnet4/ism/verification.json +++ b/typescript/infra/config/environments/testnet4/ism/verification.json @@ -1,1498 +1,1560 @@ { "alfajores": [ { - "name": "StaticMultisigIsmFactory", "address": "0x9AF85731EDd41E2E50F81Ef8a0A69D2fB836EDf9", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticMultisigIsmFactory" }, { - "name": "StaticAggregationIsmFactory", "address": "0xBEd8Fd6d5c6cBd878479C25f4725C7c842a43821", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticAggregationIsmFactory" }, { - "name": "DomainRoutingIsmFactory", "address": "0x98F44EA5b9cA6aa02a5B75f31E0621083d9096a2", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "DomainRoutingIsmFactory" }, { - "name": "StaticMerkleRootMultisigIsmFactory", "address": "0x6525Ac4008E38e0E70DaEf59d5f0e1721bd8aA83", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticMerkleRootMultisigIsmFactory" }, { - "name": "StaticMessageIdMultisigIsmFactory", "address": "0x4C739E01f295B70762C0bA9D86123E1775C2f703", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticMessageIdMultisigIsmFactory" }, { - "name": "StaticMerkleRootMultisigIsmFactory", "address": "0x9A574458497FCaB5E5673a2610C108725002DbA3", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticMerkleRootMultisigIsmFactory" }, { - "name": "StaticMerkleRootMultisigIsmFactory", "address": "0xa9C7e306C0941896CA1fd528aA59089571D8D67E", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticMerkleRootMultisigIsmFactory" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x826Ab153e944cE1B251aBD174071DF70d71132D7" + "address": "0x826Ab153e944cE1B251aBD174071DF70d71132D7", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticMessageIdMultisigIsmFactory", "address": "0xC1b8c0e56D6a34940Ee2B86172450B54AFd633A7", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticMessageIdMultisigIsmFactory" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0x78ebeCE0fCAa39F0F0C422E87EBd6BcB708aa763" + "address": "0x78ebeCE0fCAa39F0F0C422E87EBd6BcB708aa763", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationIsmFactory", "address": "0x4bE8AC22f506B1504C93C3A5b1579C5e7c550D9C", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticAggregationIsmFactory" }, { - "name": "StaticAggregationIsm", - "address": "0xd93F514f36d528DCDc3af1C92788f1bf1f480A7f" + "address": "0xd93F514f36d528DCDc3af1C92788f1bf1f480A7f", + "name": "StaticAggregationIsm" }, { - "name": "StaticAggregationHookFactory", "address": "0x71bB34Ee833467443628CEdFAA188B2387827Cee", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticAggregationHookFactory" }, { - "name": "StaticAggregationHook", - "address": "0x019e0a350D85eF67250dD88CD6477ad5E0c6E31e" + "address": "0x019e0a350D85eF67250dD88CD6477ad5E0c6E31e", + "name": "StaticAggregationHook" }, { - "name": "DomainRoutingIsmFactory", "address": "0x37308d498bc7B0f002cb02Cf8fA01770dC2169c8", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "DomainRoutingIsmFactory" }, { - "name": "DomaingRoutingIsm", - "address": "0x94087079Bf22D8e2BCE02e2180e738C3F21dD44E" + "address": "0x94087079Bf22D8e2BCE02e2180e738C3F21dD44E", + "name": "DomaingRoutingIsm" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x826Ab153e944cE1B251aBD174071DF70d71132D7" + "address": "0x826Ab153e944cE1B251aBD174071DF70d71132D7", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0x78ebeCE0fCAa39F0F0C422E87EBd6BcB708aa763" + "address": "0x78ebeCE0fCAa39F0F0C422E87EBd6BcB708aa763", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationIsm", - "address": "0xd93F514f36d528DCDc3af1C92788f1bf1f480A7f" + "address": "0xd93F514f36d528DCDc3af1C92788f1bf1f480A7f", + "name": "StaticAggregationIsm" }, { - "name": "StaticAggregationHook", - "address": "0x019e0a350D85eF67250dD88CD6477ad5E0c6E31e" + "address": "0x019e0a350D85eF67250dD88CD6477ad5E0c6E31e", + "name": "StaticAggregationHook" }, { - "name": "DomaingRoutingIsm", - "address": "0x94087079Bf22D8e2BCE02e2180e738C3F21dD44E" + "address": "0x94087079Bf22D8e2BCE02e2180e738C3F21dD44E", + "name": "DomaingRoutingIsm" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x826Ab153e944cE1B251aBD174071DF70d71132D7" + "address": "0x826Ab153e944cE1B251aBD174071DF70d71132D7", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0x78ebeCE0fCAa39F0F0C422E87EBd6BcB708aa763" + "address": "0x78ebeCE0fCAa39F0F0C422E87EBd6BcB708aa763", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationIsm", - "address": "0xd93F514f36d528DCDc3af1C92788f1bf1f480A7f" + "address": "0xd93F514f36d528DCDc3af1C92788f1bf1f480A7f", + "name": "StaticAggregationIsm" }, { - "name": "StaticAggregationHook", - "address": "0x019e0a350D85eF67250dD88CD6477ad5E0c6E31e" + "address": "0x019e0a350D85eF67250dD88CD6477ad5E0c6E31e", + "name": "StaticAggregationHook" }, { - "name": "DomaingRoutingIsm", - "address": "0x94087079Bf22D8e2BCE02e2180e738C3F21dD44E" + "address": "0x94087079Bf22D8e2BCE02e2180e738C3F21dD44E", + "name": "DomaingRoutingIsm" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x826Ab153e944cE1B251aBD174071DF70d71132D7" + "address": "0x826Ab153e944cE1B251aBD174071DF70d71132D7", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0x78ebeCE0fCAa39F0F0C422E87EBd6BcB708aa763" + "address": "0x78ebeCE0fCAa39F0F0C422E87EBd6BcB708aa763", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationIsm", - "address": "0xd93F514f36d528DCDc3af1C92788f1bf1f480A7f" + "address": "0xd93F514f36d528DCDc3af1C92788f1bf1f480A7f", + "name": "StaticAggregationIsm" }, { - "name": "StaticAggregationHook", - "address": "0x019e0a350D85eF67250dD88CD6477ad5E0c6E31e" + "address": "0x019e0a350D85eF67250dD88CD6477ad5E0c6E31e", + "name": "StaticAggregationHook" }, { - "name": "DomaingRoutingIsm", - "address": "0x94087079Bf22D8e2BCE02e2180e738C3F21dD44E" + "address": "0x94087079Bf22D8e2BCE02e2180e738C3F21dD44E", + "name": "DomaingRoutingIsm" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x826Ab153e944cE1B251aBD174071DF70d71132D7" + "address": "0x826Ab153e944cE1B251aBD174071DF70d71132D7", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0x78ebeCE0fCAa39F0F0C422E87EBd6BcB708aa763" + "address": "0x78ebeCE0fCAa39F0F0C422E87EBd6BcB708aa763", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationIsm", - "address": "0xd93F514f36d528DCDc3af1C92788f1bf1f480A7f" + "address": "0xd93F514f36d528DCDc3af1C92788f1bf1f480A7f", + "name": "StaticAggregationIsm" }, { - "name": "StaticAggregationHook", - "address": "0x019e0a350D85eF67250dD88CD6477ad5E0c6E31e" + "address": "0x019e0a350D85eF67250dD88CD6477ad5E0c6E31e", + "name": "StaticAggregationHook" }, { - "name": "DomaingRoutingIsm", - "address": "0x94087079Bf22D8e2BCE02e2180e738C3F21dD44E" + "address": "0x94087079Bf22D8e2BCE02e2180e738C3F21dD44E", + "name": "DomaingRoutingIsm" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x826Ab153e944cE1B251aBD174071DF70d71132D7" + "address": "0x826Ab153e944cE1B251aBD174071DF70d71132D7", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0x78ebeCE0fCAa39F0F0C422E87EBd6BcB708aa763" + "address": "0x78ebeCE0fCAa39F0F0C422E87EBd6BcB708aa763", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationIsm", - "address": "0xd93F514f36d528DCDc3af1C92788f1bf1f480A7f" + "address": "0xd93F514f36d528DCDc3af1C92788f1bf1f480A7f", + "name": "StaticAggregationIsm" }, { - "name": "StaticAggregationHook", - "address": "0x019e0a350D85eF67250dD88CD6477ad5E0c6E31e" + "address": "0x019e0a350D85eF67250dD88CD6477ad5E0c6E31e", + "name": "StaticAggregationHook" }, { - "name": "DomaingRoutingIsm", - "address": "0x94087079Bf22D8e2BCE02e2180e738C3F21dD44E" + "address": "0x94087079Bf22D8e2BCE02e2180e738C3F21dD44E", + "name": "DomaingRoutingIsm" }, { - "name": "StaticMerkleRootMultisigIsm", "address": "0x826Ab153e944cE1B251aBD174071DF70d71132D7", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticMessageIdMultisigIsm", "address": "0x78ebeCE0fCAa39F0F0C422E87EBd6BcB708aa763", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationIsm", "address": "0xd93F514f36d528DCDc3af1C92788f1bf1f480A7f", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticAggregationIsm" }, { - "name": "StaticAggregationHook", "address": "0x019e0a350D85eF67250dD88CD6477ad5E0c6E31e", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticAggregationHook" }, { - "name": "DomaingRoutingIsm", "address": "0xd5ab9FaC601Ed731d5e9c87E5977D2e5fD380666", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "DomaingRoutingIsm" }, { - "name": "StaticMerkleRootMultisigIsm", "address": "0x826Ab153e944cE1B251aBD174071DF70d71132D7", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticMessageIdMultisigIsm", "address": "0x78ebeCE0fCAa39F0F0C422E87EBd6BcB708aa763", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationIsm", "address": "0xd93F514f36d528DCDc3af1C92788f1bf1f480A7f", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticAggregationIsm" }, { - "name": "StaticAggregationHook", "address": "0x019e0a350D85eF67250dD88CD6477ad5E0c6E31e", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticAggregationHook" }, { - "name": "DomaingRoutingIsm", "address": "0xd5ab9FaC601Ed731d5e9c87E5977D2e5fD380666", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "DomaingRoutingIsm" }, { - "name": "StaticMerkleRootMultisigIsm", "address": "0x826Ab153e944cE1B251aBD174071DF70d71132D7", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticMessageIdMultisigIsm", "address": "0x78ebeCE0fCAa39F0F0C422E87EBd6BcB708aa763", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationIsm", "address": "0xd93F514f36d528DCDc3af1C92788f1bf1f480A7f", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticAggregationIsm" }, { - "name": "StaticAggregationHook", "address": "0x019e0a350D85eF67250dD88CD6477ad5E0c6E31e", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticAggregationHook" }, { - "name": "DomaingRoutingIsm", "address": "0xd5ab9FaC601Ed731d5e9c87E5977D2e5fD380666", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "DomaingRoutingIsm" }, { - "name": "StaticMerkleRootMultisigIsm", "address": "0x826Ab153e944cE1B251aBD174071DF70d71132D7", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticMessageIdMultisigIsm", "address": "0x78ebeCE0fCAa39F0F0C422E87EBd6BcB708aa763", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationIsm", "address": "0xd93F514f36d528DCDc3af1C92788f1bf1f480A7f", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticAggregationIsm" }, { - "name": "StaticAggregationHook", "address": "0x019e0a350D85eF67250dD88CD6477ad5E0c6E31e", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticAggregationHook" }, { - "name": "DomaingRoutingIsm", "address": "0xd5ab9FaC601Ed731d5e9c87E5977D2e5fD380666", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "DomaingRoutingIsm" } ], - "fuji": [ + "bsctestnet": [ { - "name": "StaticMultisigIsmFactory", - "address": "0x094652a8ea2153A03916771a778E7b66839A4F58", + "address": "0xfb6B94750e1307719892fBC21AC7A0C74A467869", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticMultisigIsmFactory" }, { - "name": "StaticAggregationIsmFactory", - "address": "0x9fB5D10C07569F2EBdc8ec4432B3a52b6d0ad9A0", + "address": "0xda72972291172B9966Dec7606d45d72e2b9f2470", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticAggregationIsmFactory" }, { - "name": "DomainRoutingIsmFactory", - "address": "0xB24C91238eA30D59CF58CEB8dd5e4eaf70544d47", + "address": "0x0CA314006fe0e7EF88ad2Bb69a7421aB2f1C5288", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "DomainRoutingIsmFactory" }, { - "name": "StaticMerkleRootMultisigIsmFactory", - "address": "0x76a1aaE73e9D837ceF10Ac5AF5AfDD30d7612f98", + "address": "0x8DA546024850D998Be3b65204c0F0f63C1f3B0A1", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticMerkleRootMultisigIsmFactory" }, { - "name": "StaticMessageIdMultisigIsmFactory", - "address": "0xA1e6d12a3F5F7e05E4D6cb39E71534F27fE29561", + "address": "0x7Bc0bb71aE0E9bDC0Ac53e932870728D95FA28bF", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticMessageIdMultisigIsmFactory" }, { - "name": "StaticMerkleRootMultisigIsmFactory", - "address": "0xc25e8cE0960fa2573AFa591484F2cA6e4497C2e5", + "address": "0x3E235B90197E1D6b5DB5ad5aD49f2c1ED6406382", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticMerkleRootMultisigIsmFactory" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x074D3C4249AFdac44B70d1D220F358Ff895F7a80" + "address": "0x9b13f3d75797951f463D61092Bba7947c8cEb25c", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticMerkleRootMultisigIsmFactory", - "address": "0x93F50Ac4E5663DAAb03508008d592f6260964f62", + "address": "0x0D96aF0c01c4bbbadaaF989Eb489c8783F35B763", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticMessageIdMultisigIsmFactory" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x0683D0258A162a87E1F25C454BaA4e9E9DE46B0a" + "address": "0x25069c25466B1F21E0D22f467a5f91bc7b24b515", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticMessageIdMultisigIsmFactory", - "address": "0x90e1F9918F304645e4F6324E5C0EAc70138C84Ce", + "address": "0x40613dE82d672605Ab051C64079022Bb4F8bDE4f", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticAggregationIsmFactory" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0xd3262339D39103a4b41379b183b0311B78c9a11F" + "address": "0x25adfb5Ddf378EC77cd5842Ff33B96277E4DD29b", + "name": "StaticAggregationIsm" }, { - "name": "StaticAggregationIsmFactory", - "address": "0xF588129ed84F219A1f0f921bE7Aa1B2176516858", + "address": "0xa1145B39F1c7Ef9aA593BC1DB1634b00CC020942", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticAggregationHookFactory" }, { - "name": "StaticAggregationIsm", - "address": "0x6716Ef4e72c74e66eB40198B16b4379a763fCEd8" + "address": "0x3938ED4c6b5a7f9947C44e58e36a864F03228175", + "name": "StaticAggregationHook" }, { - "name": "StaticAggregationHookFactory", - "address": "0x99554CC33cBCd6EDDd2f3fc9c7C9194Cb3b5df1E", + "address": "0xea12ECFD1f241da323e93F12b4ed936403990190", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "DomainRoutingIsmFactory" }, { - "name": "StaticAggregationHook", - "address": "0xBDD7Cfb61D7ae89343b428D3E527BDA9705c4EAC" + "address": "0xf9E264C10E9176c97c22790E0Ece8D98927Bca71", + "name": "DomaingRoutingIsm" }, { - "name": "DomainRoutingIsmFactory", - "address": "0xf9271189Cb30AD1F272f1A9EB2272224135B9350", - "constructorArguments": "", - "isProxy": false + "address": "0x9b13f3d75797951f463D61092Bba7947c8cEb25c", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "DomaingRoutingIsm", - "address": "0x930c36A0c978B0b86B544859692A1D94c5148204" + "address": "0x25069c25466B1F21E0D22f467a5f91bc7b24b515", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x0683D0258A162a87E1F25C454BaA4e9E9DE46B0a" + "address": "0x25adfb5Ddf378EC77cd5842Ff33B96277E4DD29b", + "name": "StaticAggregationIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0xd3262339D39103a4b41379b183b0311B78c9a11F" + "address": "0x3938ED4c6b5a7f9947C44e58e36a864F03228175", + "name": "StaticAggregationHook" }, { - "name": "StaticAggregationIsm", - "address": "0x6716Ef4e72c74e66eB40198B16b4379a763fCEd8" + "address": "0xf9E264C10E9176c97c22790E0Ece8D98927Bca71", + "name": "DomaingRoutingIsm" }, { - "name": "StaticAggregationHook", - "address": "0xBDD7Cfb61D7ae89343b428D3E527BDA9705c4EAC" + "address": "0x9b13f3d75797951f463D61092Bba7947c8cEb25c", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "DomaingRoutingIsm", - "address": "0x930c36A0c978B0b86B544859692A1D94c5148204" + "address": "0x25069c25466B1F21E0D22f467a5f91bc7b24b515", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x0683D0258A162a87E1F25C454BaA4e9E9DE46B0a" + "address": "0x25adfb5Ddf378EC77cd5842Ff33B96277E4DD29b", + "name": "StaticAggregationIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0xd3262339D39103a4b41379b183b0311B78c9a11F" + "address": "0x3938ED4c6b5a7f9947C44e58e36a864F03228175", + "name": "StaticAggregationHook" }, { - "name": "StaticAggregationIsm", - "address": "0x6716Ef4e72c74e66eB40198B16b4379a763fCEd8" + "address": "0xf9E264C10E9176c97c22790E0Ece8D98927Bca71", + "name": "DomaingRoutingIsm" }, { - "name": "StaticAggregationHook", - "address": "0xBDD7Cfb61D7ae89343b428D3E527BDA9705c4EAC" + "address": "0x9b13f3d75797951f463D61092Bba7947c8cEb25c", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "DomaingRoutingIsm", - "address": "0x930c36A0c978B0b86B544859692A1D94c5148204" + "address": "0x25069c25466B1F21E0D22f467a5f91bc7b24b515", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x0683D0258A162a87E1F25C454BaA4e9E9DE46B0a" + "address": "0x25adfb5Ddf378EC77cd5842Ff33B96277E4DD29b", + "name": "StaticAggregationIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0xd3262339D39103a4b41379b183b0311B78c9a11F" + "address": "0x3938ED4c6b5a7f9947C44e58e36a864F03228175", + "name": "StaticAggregationHook" }, { - "name": "StaticAggregationIsm", - "address": "0x6716Ef4e72c74e66eB40198B16b4379a763fCEd8" + "address": "0xf9E264C10E9176c97c22790E0Ece8D98927Bca71", + "name": "DomaingRoutingIsm" }, { - "name": "StaticAggregationHook", - "address": "0xBDD7Cfb61D7ae89343b428D3E527BDA9705c4EAC" + "address": "0x9b13f3d75797951f463D61092Bba7947c8cEb25c", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "DomaingRoutingIsm", - "address": "0x930c36A0c978B0b86B544859692A1D94c5148204" + "address": "0x25069c25466B1F21E0D22f467a5f91bc7b24b515", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x0683D0258A162a87E1F25C454BaA4e9E9DE46B0a" + "address": "0x25adfb5Ddf378EC77cd5842Ff33B96277E4DD29b", + "name": "StaticAggregationIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0xd3262339D39103a4b41379b183b0311B78c9a11F" + "address": "0x3938ED4c6b5a7f9947C44e58e36a864F03228175", + "name": "StaticAggregationHook" }, { - "name": "StaticAggregationIsm", - "address": "0x6716Ef4e72c74e66eB40198B16b4379a763fCEd8" + "address": "0xf9E264C10E9176c97c22790E0Ece8D98927Bca71", + "name": "DomaingRoutingIsm" }, { - "name": "StaticAggregationHook", - "address": "0xBDD7Cfb61D7ae89343b428D3E527BDA9705c4EAC" + "address": "0x9b13f3d75797951f463D61092Bba7947c8cEb25c", + "constructorArguments": "", + "isProxy": true, + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "DomaingRoutingIsm", - "address": "0x930c36A0c978B0b86B544859692A1D94c5148204" + "address": "0x25069c25466B1F21E0D22f467a5f91bc7b24b515", + "constructorArguments": "", + "isProxy": true, + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x0683D0258A162a87E1F25C454BaA4e9E9DE46B0a" + "address": "0x25adfb5Ddf378EC77cd5842Ff33B96277E4DD29b", + "constructorArguments": "", + "isProxy": true, + "name": "StaticAggregationIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0xd3262339D39103a4b41379b183b0311B78c9a11F" + "address": "0x3938ED4c6b5a7f9947C44e58e36a864F03228175", + "constructorArguments": "", + "isProxy": true, + "name": "StaticAggregationHook" }, { - "name": "StaticAggregationIsm", - "address": "0x6716Ef4e72c74e66eB40198B16b4379a763fCEd8" + "address": "0x9b13f3d75797951f463D61092Bba7947c8cEb25c", + "constructorArguments": "", + "isProxy": true, + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticAggregationHook", - "address": "0xBDD7Cfb61D7ae89343b428D3E527BDA9705c4EAC" + "address": "0x25069c25466B1F21E0D22f467a5f91bc7b24b515", + "constructorArguments": "", + "isProxy": true, + "name": "StaticMessageIdMultisigIsm" }, { - "name": "DomaingRoutingIsm", - "address": "0x930c36A0c978B0b86B544859692A1D94c5148204" + "address": "0x25adfb5Ddf378EC77cd5842Ff33B96277E4DD29b", + "constructorArguments": "", + "isProxy": true, + "name": "StaticAggregationIsm" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x0683D0258A162a87E1F25C454BaA4e9E9DE46B0a", + "address": "0x3938ED4c6b5a7f9947C44e58e36a864F03228175", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticAggregationHook" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0xd3262339D39103a4b41379b183b0311B78c9a11F", + "address": "0x9b13f3d75797951f463D61092Bba7947c8cEb25c", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticAggregationIsm", - "address": "0x6716Ef4e72c74e66eB40198B16b4379a763fCEd8", + "address": "0x25069c25466B1F21E0D22f467a5f91bc7b24b515", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationHook", - "address": "0xBDD7Cfb61D7ae89343b428D3E527BDA9705c4EAC", + "address": "0x25adfb5Ddf378EC77cd5842Ff33B96277E4DD29b", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticAggregationIsm" }, { - "name": "DomainRoutingIsmFactory", - "address": "0x683a81E0e1a238dcA7341e04c08d3bba6f0Cb74f", + "address": "0x3938ED4c6b5a7f9947C44e58e36a864F03228175", "constructorArguments": "", - "isProxy": false + "isProxy": true, + "name": "StaticAggregationHook" + } + ], + "fuji": [ + { + "address": "0x094652a8ea2153A03916771a778E7b66839A4F58", + "constructorArguments": "", + "isProxy": false, + "name": "StaticMultisigIsmFactory" }, { - "name": "DomaingRoutingIsm", - "address": "0xD47593C21C2429de56AcA808bb2Bbc236c6311DE", + "address": "0x9fB5D10C07569F2EBdc8ec4432B3a52b6d0ad9A0", "constructorArguments": "", - "isProxy": true + "isProxy": false, + "name": "StaticAggregationIsmFactory" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x0683D0258A162a87E1F25C454BaA4e9E9DE46B0a", + "address": "0xB24C91238eA30D59CF58CEB8dd5e4eaf70544d47", "constructorArguments": "", - "isProxy": true + "isProxy": false, + "name": "DomainRoutingIsmFactory" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0xd3262339D39103a4b41379b183b0311B78c9a11F", + "address": "0x76a1aaE73e9D837ceF10Ac5AF5AfDD30d7612f98", "constructorArguments": "", - "isProxy": true + "isProxy": false, + "name": "StaticMerkleRootMultisigIsmFactory" }, { - "name": "StaticAggregationIsm", - "address": "0x6716Ef4e72c74e66eB40198B16b4379a763fCEd8", + "address": "0xA1e6d12a3F5F7e05E4D6cb39E71534F27fE29561", "constructorArguments": "", - "isProxy": true + "isProxy": false, + "name": "StaticMessageIdMultisigIsmFactory" }, { - "name": "StaticAggregationHook", - "address": "0xBDD7Cfb61D7ae89343b428D3E527BDA9705c4EAC", + "address": "0xc25e8cE0960fa2573AFa591484F2cA6e4497C2e5", "constructorArguments": "", - "isProxy": true + "isProxy": false, + "name": "StaticMerkleRootMultisigIsmFactory" }, { - "name": "DomaingRoutingIsm", - "address": "0xD47593C21C2429de56AcA808bb2Bbc236c6311DE", + "address": "0x074D3C4249AFdac44B70d1D220F358Ff895F7a80", + "name": "StaticMerkleRootMultisigIsm" + }, + { + "address": "0x93F50Ac4E5663DAAb03508008d592f6260964f62", "constructorArguments": "", - "isProxy": true + "isProxy": false, + "name": "StaticMerkleRootMultisigIsmFactory" }, { - "name": "StaticMerkleRootMultisigIsm", "address": "0x0683D0258A162a87E1F25C454BaA4e9E9DE46B0a", + "name": "StaticMerkleRootMultisigIsm" + }, + { + "address": "0x90e1F9918F304645e4F6324E5C0EAc70138C84Ce", "constructorArguments": "", - "isProxy": true + "isProxy": false, + "name": "StaticMessageIdMultisigIsmFactory" }, { - "name": "StaticMessageIdMultisigIsm", "address": "0xd3262339D39103a4b41379b183b0311B78c9a11F", + "name": "StaticMessageIdMultisigIsm" + }, + { + "address": "0xF588129ed84F219A1f0f921bE7Aa1B2176516858", "constructorArguments": "", - "isProxy": true + "isProxy": false, + "name": "StaticAggregationIsmFactory" }, { - "name": "StaticAggregationIsm", "address": "0x6716Ef4e72c74e66eB40198B16b4379a763fCEd8", + "name": "StaticAggregationIsm" + }, + { + "address": "0x99554CC33cBCd6EDDd2f3fc9c7C9194Cb3b5df1E", "constructorArguments": "", - "isProxy": true + "isProxy": false, + "name": "StaticAggregationHookFactory" }, { - "name": "StaticAggregationHook", "address": "0xBDD7Cfb61D7ae89343b428D3E527BDA9705c4EAC", - "constructorArguments": "", - "isProxy": true + "name": "StaticAggregationHook" }, { - "name": "DomaingRoutingIsm", - "address": "0xD47593C21C2429de56AcA808bb2Bbc236c6311DE", + "address": "0xf9271189Cb30AD1F272f1A9EB2272224135B9350", "constructorArguments": "", - "isProxy": true + "isProxy": false, + "name": "DomainRoutingIsmFactory" + }, + { + "address": "0x930c36A0c978B0b86B544859692A1D94c5148204", + "name": "DomaingRoutingIsm" }, { - "name": "StaticMerkleRootMultisigIsm", "address": "0x0683D0258A162a87E1F25C454BaA4e9E9DE46B0a", - "constructorArguments": "", - "isProxy": true + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticMessageIdMultisigIsm", "address": "0xd3262339D39103a4b41379b183b0311B78c9a11F", - "constructorArguments": "", - "isProxy": true + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationIsm", "address": "0x6716Ef4e72c74e66eB40198B16b4379a763fCEd8", - "constructorArguments": "", - "isProxy": true + "name": "StaticAggregationIsm" }, { - "name": "StaticAggregationHook", "address": "0xBDD7Cfb61D7ae89343b428D3E527BDA9705c4EAC", - "constructorArguments": "", - "isProxy": true + "name": "StaticAggregationHook" }, { - "name": "DomaingRoutingIsm", - "address": "0xD47593C21C2429de56AcA808bb2Bbc236c6311DE", - "constructorArguments": "", - "isProxy": true - } - ], - "bsctestnet": [ + "address": "0x930c36A0c978B0b86B544859692A1D94c5148204", + "name": "DomaingRoutingIsm" + }, { - "name": "StaticMultisigIsmFactory", - "address": "0xfb6B94750e1307719892fBC21AC7A0C74A467869", - "constructorArguments": "", - "isProxy": false + "address": "0x0683D0258A162a87E1F25C454BaA4e9E9DE46B0a", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticAggregationIsmFactory", - "address": "0xda72972291172B9966Dec7606d45d72e2b9f2470", - "constructorArguments": "", - "isProxy": false + "address": "0xd3262339D39103a4b41379b183b0311B78c9a11F", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "DomainRoutingIsmFactory", - "address": "0x0CA314006fe0e7EF88ad2Bb69a7421aB2f1C5288", - "constructorArguments": "", - "isProxy": false + "address": "0x6716Ef4e72c74e66eB40198B16b4379a763fCEd8", + "name": "StaticAggregationIsm" }, { - "name": "StaticMerkleRootMultisigIsmFactory", - "address": "0x8DA546024850D998Be3b65204c0F0f63C1f3B0A1", - "constructorArguments": "", - "isProxy": false + "address": "0xBDD7Cfb61D7ae89343b428D3E527BDA9705c4EAC", + "name": "StaticAggregationHook" }, { - "name": "StaticMessageIdMultisigIsmFactory", - "address": "0x7Bc0bb71aE0E9bDC0Ac53e932870728D95FA28bF", - "constructorArguments": "", - "isProxy": false + "address": "0x930c36A0c978B0b86B544859692A1D94c5148204", + "name": "DomaingRoutingIsm" }, { - "name": "StaticMerkleRootMultisigIsmFactory", - "address": "0x3E235B90197E1D6b5DB5ad5aD49f2c1ED6406382", - "constructorArguments": "", - "isProxy": false + "address": "0x0683D0258A162a87E1F25C454BaA4e9E9DE46B0a", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x9b13f3d75797951f463D61092Bba7947c8cEb25c" + "address": "0xd3262339D39103a4b41379b183b0311B78c9a11F", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticMessageIdMultisigIsmFactory", - "address": "0x0D96aF0c01c4bbbadaaF989Eb489c8783F35B763", - "constructorArguments": "", - "isProxy": false + "address": "0x6716Ef4e72c74e66eB40198B16b4379a763fCEd8", + "name": "StaticAggregationIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0x25069c25466B1F21E0D22f467a5f91bc7b24b515" + "address": "0xBDD7Cfb61D7ae89343b428D3E527BDA9705c4EAC", + "name": "StaticAggregationHook" }, { - "name": "StaticAggregationIsmFactory", - "address": "0x40613dE82d672605Ab051C64079022Bb4F8bDE4f", - "constructorArguments": "", - "isProxy": false + "address": "0x930c36A0c978B0b86B544859692A1D94c5148204", + "name": "DomaingRoutingIsm" }, { - "name": "StaticAggregationIsm", - "address": "0x25adfb5Ddf378EC77cd5842Ff33B96277E4DD29b" + "address": "0x0683D0258A162a87E1F25C454BaA4e9E9DE46B0a", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticAggregationHookFactory", - "address": "0xa1145B39F1c7Ef9aA593BC1DB1634b00CC020942", - "constructorArguments": "", - "isProxy": false + "address": "0xd3262339D39103a4b41379b183b0311B78c9a11F", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationHook", - "address": "0x3938ED4c6b5a7f9947C44e58e36a864F03228175" + "address": "0x6716Ef4e72c74e66eB40198B16b4379a763fCEd8", + "name": "StaticAggregationIsm" }, { - "name": "DomainRoutingIsmFactory", - "address": "0xea12ECFD1f241da323e93F12b4ed936403990190", - "constructorArguments": "", - "isProxy": false + "address": "0xBDD7Cfb61D7ae89343b428D3E527BDA9705c4EAC", + "name": "StaticAggregationHook" }, { - "name": "DomaingRoutingIsm", - "address": "0xf9E264C10E9176c97c22790E0Ece8D98927Bca71" + "address": "0x930c36A0c978B0b86B544859692A1D94c5148204", + "name": "DomaingRoutingIsm" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x9b13f3d75797951f463D61092Bba7947c8cEb25c" + "address": "0x0683D0258A162a87E1F25C454BaA4e9E9DE46B0a", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0x25069c25466B1F21E0D22f467a5f91bc7b24b515" + "address": "0xd3262339D39103a4b41379b183b0311B78c9a11F", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationIsm", - "address": "0x25adfb5Ddf378EC77cd5842Ff33B96277E4DD29b" + "address": "0x6716Ef4e72c74e66eB40198B16b4379a763fCEd8", + "name": "StaticAggregationIsm" }, { - "name": "StaticAggregationHook", - "address": "0x3938ED4c6b5a7f9947C44e58e36a864F03228175" + "address": "0xBDD7Cfb61D7ae89343b428D3E527BDA9705c4EAC", + "name": "StaticAggregationHook" }, { - "name": "DomaingRoutingIsm", - "address": "0xf9E264C10E9176c97c22790E0Ece8D98927Bca71" + "address": "0x930c36A0c978B0b86B544859692A1D94c5148204", + "name": "DomaingRoutingIsm" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x9b13f3d75797951f463D61092Bba7947c8cEb25c" + "address": "0x0683D0258A162a87E1F25C454BaA4e9E9DE46B0a", + "constructorArguments": "", + "isProxy": true, + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0x25069c25466B1F21E0D22f467a5f91bc7b24b515" + "address": "0xd3262339D39103a4b41379b183b0311B78c9a11F", + "constructorArguments": "", + "isProxy": true, + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationIsm", - "address": "0x25adfb5Ddf378EC77cd5842Ff33B96277E4DD29b" + "address": "0x6716Ef4e72c74e66eB40198B16b4379a763fCEd8", + "constructorArguments": "", + "isProxy": true, + "name": "StaticAggregationIsm" }, { - "name": "StaticAggregationHook", - "address": "0x3938ED4c6b5a7f9947C44e58e36a864F03228175" + "address": "0xBDD7Cfb61D7ae89343b428D3E527BDA9705c4EAC", + "constructorArguments": "", + "isProxy": true, + "name": "StaticAggregationHook" }, { - "name": "DomaingRoutingIsm", - "address": "0xf9E264C10E9176c97c22790E0Ece8D98927Bca71" + "address": "0x683a81E0e1a238dcA7341e04c08d3bba6f0Cb74f", + "constructorArguments": "", + "isProxy": false, + "name": "DomainRoutingIsmFactory" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x9b13f3d75797951f463D61092Bba7947c8cEb25c" + "address": "0xD47593C21C2429de56AcA808bb2Bbc236c6311DE", + "constructorArguments": "", + "isProxy": true, + "name": "DomaingRoutingIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0x25069c25466B1F21E0D22f467a5f91bc7b24b515" + "address": "0x0683D0258A162a87E1F25C454BaA4e9E9DE46B0a", + "constructorArguments": "", + "isProxy": true, + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticAggregationIsm", - "address": "0x25adfb5Ddf378EC77cd5842Ff33B96277E4DD29b" + "address": "0xd3262339D39103a4b41379b183b0311B78c9a11F", + "constructorArguments": "", + "isProxy": true, + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationHook", - "address": "0x3938ED4c6b5a7f9947C44e58e36a864F03228175" + "address": "0x6716Ef4e72c74e66eB40198B16b4379a763fCEd8", + "constructorArguments": "", + "isProxy": true, + "name": "StaticAggregationIsm" }, { - "name": "DomaingRoutingIsm", - "address": "0xf9E264C10E9176c97c22790E0Ece8D98927Bca71" + "address": "0xBDD7Cfb61D7ae89343b428D3E527BDA9705c4EAC", + "constructorArguments": "", + "isProxy": true, + "name": "StaticAggregationHook" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x9b13f3d75797951f463D61092Bba7947c8cEb25c" + "address": "0xD47593C21C2429de56AcA808bb2Bbc236c6311DE", + "constructorArguments": "", + "isProxy": true, + "name": "DomaingRoutingIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0x25069c25466B1F21E0D22f467a5f91bc7b24b515" + "address": "0x0683D0258A162a87E1F25C454BaA4e9E9DE46B0a", + "constructorArguments": "", + "isProxy": true, + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticAggregationIsm", - "address": "0x25adfb5Ddf378EC77cd5842Ff33B96277E4DD29b" + "address": "0xd3262339D39103a4b41379b183b0311B78c9a11F", + "constructorArguments": "", + "isProxy": true, + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationHook", - "address": "0x3938ED4c6b5a7f9947C44e58e36a864F03228175" + "address": "0x6716Ef4e72c74e66eB40198B16b4379a763fCEd8", + "constructorArguments": "", + "isProxy": true, + "name": "StaticAggregationIsm" }, { - "name": "DomaingRoutingIsm", - "address": "0xf9E264C10E9176c97c22790E0Ece8D98927Bca71" + "address": "0xBDD7Cfb61D7ae89343b428D3E527BDA9705c4EAC", + "constructorArguments": "", + "isProxy": true, + "name": "StaticAggregationHook" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x9b13f3d75797951f463D61092Bba7947c8cEb25c", + "address": "0xD47593C21C2429de56AcA808bb2Bbc236c6311DE", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "DomaingRoutingIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0x25069c25466B1F21E0D22f467a5f91bc7b24b515", + "address": "0x0683D0258A162a87E1F25C454BaA4e9E9DE46B0a", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticAggregationIsm", - "address": "0x25adfb5Ddf378EC77cd5842Ff33B96277E4DD29b", + "address": "0xd3262339D39103a4b41379b183b0311B78c9a11F", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationHook", - "address": "0x3938ED4c6b5a7f9947C44e58e36a864F03228175", + "address": "0x6716Ef4e72c74e66eB40198B16b4379a763fCEd8", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticAggregationIsm" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x9b13f3d75797951f463D61092Bba7947c8cEb25c", + "address": "0xBDD7Cfb61D7ae89343b428D3E527BDA9705c4EAC", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticAggregationHook" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0x25069c25466B1F21E0D22f467a5f91bc7b24b515", + "address": "0xD47593C21C2429de56AcA808bb2Bbc236c6311DE", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "DomaingRoutingIsm" + } + ], + "holesky": [ + { + "address": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", + "constructorArguments": "", + "isProxy": false, + "name": "StaticMerkleRootMultisigIsmFactory" }, { - "name": "StaticAggregationIsm", - "address": "0x25adfb5Ddf378EC77cd5842Ff33B96277E4DD29b", + "address": "0x7fFe8C9c17F46F94D784E148FbadD4bF66477722", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticAggregationHook", - "address": "0x3938ED4c6b5a7f9947C44e58e36a864F03228175", + "address": "0x6966b0E55883d49BFB24539356a2f8A673E02039", "constructorArguments": "", - "isProxy": true + "isProxy": false, + "name": "StaticMessageIdMultisigIsmFactory" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x9b13f3d75797951f463D61092Bba7947c8cEb25c", + "address": "0x4863236F3a05A1A1F0850fF8cd09afeBAE82d953", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0x25069c25466B1F21E0D22f467a5f91bc7b24b515", + "address": "0x54148470292C24345fb828B003461a9444414517", "constructorArguments": "", - "isProxy": true + "isProxy": false, + "name": "StaticAggregationIsmFactory" }, { - "name": "StaticAggregationIsm", - "address": "0x25adfb5Ddf378EC77cd5842Ff33B96277E4DD29b", + "address": "0x10c9FF6EEE4BaD29734322467f541C84001422C2", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticAggregationIsm" }, { - "name": "StaticAggregationHook", - "address": "0x3938ED4c6b5a7f9947C44e58e36a864F03228175", + "address": "0x589C201a07c26b4725A4A829d772f24423da480B", + "constructorArguments": "", + "isProxy": false, + "name": "StaticAggregationHookFactory" + }, + { + "address": "0xD6B8D6C372e07f67FeAb75403c0Ec88E3cce7Ab7", + "constructorArguments": "", + "isProxy": true, + "name": "StaticAggregationHook" + }, + { + "address": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD", "constructorArguments": "", - "isProxy": true + "isProxy": false, + "name": "DomainRoutingIsmFactory" + }, + { + "address": "0x8CF9aB5F805072A007dAd0ae56E0099e83B14b61", + "constructorArguments": "", + "isProxy": true, + "name": "DomaingRoutingIsm" } ], - "sepolia": [ + "moonbasealpha": [ { - "name": "StaticMultisigIsmFactory", - "address": "0xD356C996277eFb7f75Ee8bd61b31cC781A12F54f", + "address": "0x4266D8Dd66D8Eb3934c8942968d1e54214D072d3", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticMultisigIsmFactory" }, { - "name": "StaticAggregationIsmFactory", - "address": "0x01812D60958798695391dacF092BAc4a715B1718", + "address": "0x759c4Eb4575B651a9f0Fb46653dd7B2F32fD7310", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticAggregationIsmFactory" }, { - "name": "DomainRoutingIsmFactory", - "address": "0xE67CfA164cDa449Ae38a0a09391eF6bCDf8e4e2c", + "address": "0x561331FafB7f2ABa77E77780178ADdD1A37bdaBD", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "DomainRoutingIsmFactory" }, { - "name": "StaticMerkleRootMultisigIsmFactory", - "address": "0xA9999B4abC373FF2BB95B8725FABC96CA883d811", + "address": "0x0616A79374e81eB1d2275eCe5837aD050f9c53f1", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticMerkleRootMultisigIsmFactory" }, { - "name": "StaticMessageIdMultisigIsmFactory", - "address": "0xCCC126d96efcc342BF2781A7d224D3AB1F25B19C", + "address": "0x3D696c38Dd958e635f9077e65b64aA9cf7c92627", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticMessageIdMultisigIsmFactory" }, { - "name": "StaticMerkleRootMultisigIsmFactory", - "address": "0x0a71AcC99967829eE305a285750017C4916Ca269", + "address": "0xA59Ba0A8D4ea5A5DC9c8B0101ba7E6eE6C3399A4", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticMerkleRootMultisigIsmFactory" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0xbe94Cd5d0973dB4A6E63Cb9e6b2b2042D99F9Bcd" + "address": "0x97B07A8BCc0d212A4C76679b303A4C6441BB6186", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticMessageIdMultisigIsmFactory", - "address": "0xFEb9585b2f948c1eD74034205a7439261a9d27DD", + "address": "0x8f919348F9C4619A196Acb5e377f49E5E2C0B569", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticMessageIdMultisigIsmFactory" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0x218C5DFb7C2bC1e051FCFebBa8C6D66Dda28617E" + "address": "0xb1Fc72f251A6b6c37CD4Ef8f9287578BCc7CE9Eb", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationIsmFactory", - "address": "0xC83e12EF2627ACE445C298e6eC418684918a6002", + "address": "0x0048FaB53526D9a0478f66D660059E3E3611FE3E", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticAggregationIsmFactory" }, { - "name": "StaticAggregationIsm", - "address": "0x3dD1aB6B71EBfEd239869365FE5B8b47E0507d89" + "address": "0x13b6Cb8d51C7e15e5fFee432CBB1d4614972A87e", + "name": "StaticAggregationIsm" }, { - "name": "StaticAggregationHookFactory", - "address": "0x160C28C92cA453570aD7C031972b58d5Dd128F72", + "address": "0x00DFB81Bfc45fa03060b605273147F274ea807E5", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticAggregationHookFactory" }, { - "name": "StaticAggregationHook", - "address": "0x4BC580FF77Dd1Eb320E96D34FddC97F0F04D3feD" + "address": "0x713496A1477a08230E526a4e9449FCCE4d6523e9", + "name": "StaticAggregationHook" }, { - "name": "DomainRoutingIsmFactory", - "address": "0x3603458990BfEb30f99E61B58427d196814D8ce1", + "address": "0x385C7f179168f5Da92c72E17AE8EF50F3874077f", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "DomainRoutingIsmFactory" }, { - "name": "DomaingRoutingIsm", - "address": "0x67543F48DBe592F1A15FD9aE37b1AbD104c70cf3" + "address": "0xCd76aC44337BbA003bb4Bed2Ac37604148F36bA9", + "name": "DomaingRoutingIsm" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0xbe94Cd5d0973dB4A6E63Cb9e6b2b2042D99F9Bcd" + "address": "0x97B07A8BCc0d212A4C76679b303A4C6441BB6186", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0x218C5DFb7C2bC1e051FCFebBa8C6D66Dda28617E" + "address": "0xb1Fc72f251A6b6c37CD4Ef8f9287578BCc7CE9Eb", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationIsm", - "address": "0x3dD1aB6B71EBfEd239869365FE5B8b47E0507d89" + "address": "0x13b6Cb8d51C7e15e5fFee432CBB1d4614972A87e", + "name": "StaticAggregationIsm" }, { - "name": "StaticAggregationHook", - "address": "0x4BC580FF77Dd1Eb320E96D34FddC97F0F04D3feD" + "address": "0x713496A1477a08230E526a4e9449FCCE4d6523e9", + "name": "StaticAggregationHook" }, { - "name": "DomaingRoutingIsm", - "address": "0x67543F48DBe592F1A15FD9aE37b1AbD104c70cf3" + "address": "0xCd76aC44337BbA003bb4Bed2Ac37604148F36bA9", + "name": "DomaingRoutingIsm" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0xbe94Cd5d0973dB4A6E63Cb9e6b2b2042D99F9Bcd" + "address": "0x97B07A8BCc0d212A4C76679b303A4C6441BB6186", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0x218C5DFb7C2bC1e051FCFebBa8C6D66Dda28617E" + "address": "0xb1Fc72f251A6b6c37CD4Ef8f9287578BCc7CE9Eb", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationIsm", - "address": "0x3dD1aB6B71EBfEd239869365FE5B8b47E0507d89" + "address": "0x13b6Cb8d51C7e15e5fFee432CBB1d4614972A87e", + "name": "StaticAggregationIsm" }, { - "name": "StaticAggregationHook", - "address": "0x4BC580FF77Dd1Eb320E96D34FddC97F0F04D3feD" + "address": "0x713496A1477a08230E526a4e9449FCCE4d6523e9", + "name": "StaticAggregationHook" }, { - "name": "DomaingRoutingIsm", - "address": "0x67543F48DBe592F1A15FD9aE37b1AbD104c70cf3" + "address": "0xCd76aC44337BbA003bb4Bed2Ac37604148F36bA9", + "name": "DomaingRoutingIsm" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0xbe94Cd5d0973dB4A6E63Cb9e6b2b2042D99F9Bcd" + "address": "0x97B07A8BCc0d212A4C76679b303A4C6441BB6186", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0x218C5DFb7C2bC1e051FCFebBa8C6D66Dda28617E" + "address": "0xb1Fc72f251A6b6c37CD4Ef8f9287578BCc7CE9Eb", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationIsm", - "address": "0x3dD1aB6B71EBfEd239869365FE5B8b47E0507d89" + "address": "0x13b6Cb8d51C7e15e5fFee432CBB1d4614972A87e", + "name": "StaticAggregationIsm" }, { - "name": "StaticAggregationHook", - "address": "0x4BC580FF77Dd1Eb320E96D34FddC97F0F04D3feD" + "address": "0x713496A1477a08230E526a4e9449FCCE4d6523e9", + "name": "StaticAggregationHook" }, { - "name": "DomaingRoutingIsm", - "address": "0x67543F48DBe592F1A15FD9aE37b1AbD104c70cf3" + "address": "0xCd76aC44337BbA003bb4Bed2Ac37604148F36bA9", + "name": "DomaingRoutingIsm" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0xbe94Cd5d0973dB4A6E63Cb9e6b2b2042D99F9Bcd" + "address": "0x97B07A8BCc0d212A4C76679b303A4C6441BB6186", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0x218C5DFb7C2bC1e051FCFebBa8C6D66Dda28617E" + "address": "0xb1Fc72f251A6b6c37CD4Ef8f9287578BCc7CE9Eb", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationIsm", - "address": "0x3dD1aB6B71EBfEd239869365FE5B8b47E0507d89" + "address": "0x13b6Cb8d51C7e15e5fFee432CBB1d4614972A87e", + "name": "StaticAggregationIsm" }, { - "name": "StaticAggregationHook", - "address": "0x4BC580FF77Dd1Eb320E96D34FddC97F0F04D3feD" + "address": "0x713496A1477a08230E526a4e9449FCCE4d6523e9", + "name": "StaticAggregationHook" }, { - "name": "DomaingRoutingIsm", - "address": "0x67543F48DBe592F1A15FD9aE37b1AbD104c70cf3" + "address": "0xCd76aC44337BbA003bb4Bed2Ac37604148F36bA9", + "name": "DomaingRoutingIsm" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0xbe94Cd5d0973dB4A6E63Cb9e6b2b2042D99F9Bcd", + "address": "0x97B07A8BCc0d212A4C76679b303A4C6441BB6186", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0x218C5DFb7C2bC1e051FCFebBa8C6D66Dda28617E", + "address": "0xb1Fc72f251A6b6c37CD4Ef8f9287578BCc7CE9Eb", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationIsm", - "address": "0x3dD1aB6B71EBfEd239869365FE5B8b47E0507d89", + "address": "0x13b6Cb8d51C7e15e5fFee432CBB1d4614972A87e", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticAggregationIsm" }, { - "name": "StaticAggregationHook", - "address": "0x4BC580FF77Dd1Eb320E96D34FddC97F0F04D3feD", + "address": "0x713496A1477a08230E526a4e9449FCCE4d6523e9", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticAggregationHook" }, { - "name": "DomainRoutingIsmFactory", - "address": "0x3F100cBBE5FD5466BdB4B3a15Ac226957e7965Ad", + "address": "0xE8F752e5C4E1A6a2e3eAfa42d44D601A22d78f2b", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "DomainRoutingIsmFactory" }, { - "name": "DomaingRoutingIsm", - "address": "0x9b0CC3BD9030CE269EF3124Bb36Cf954a490688e", + "address": "0x063C2D908EAb532Cd207F309F0fd176ae6b2e1d1", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "DomaingRoutingIsm" } ], - "moonbasealpha": [ + "plumetestnet": [ { - "name": "StaticMultisigIsmFactory", - "address": "0x4266D8Dd66D8Eb3934c8942968d1e54214D072d3", + "address": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticMerkleRootMultisigIsmFactory" }, { - "name": "StaticAggregationIsmFactory", - "address": "0x759c4Eb4575B651a9f0Fb46653dd7B2F32fD7310", + "address": "0x87935eB971eaA9826060261b07a919451dfd0409", "constructorArguments": "", - "isProxy": false + "isProxy": true, + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "DomainRoutingIsmFactory", - "address": "0x561331FafB7f2ABa77E77780178ADdD1A37bdaBD", + "address": "0x44b764045BfDC68517e10e783E69B376cef196B2", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticMessageIdMultisigIsmFactory" }, { - "name": "StaticMerkleRootMultisigIsmFactory", - "address": "0x0616A79374e81eB1d2275eCe5837aD050f9c53f1", + "address": "0xE5cA56294dA5Bd490D5Bc489B177B002ad16AF83", "constructorArguments": "", - "isProxy": false + "isProxy": true, + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticMessageIdMultisigIsmFactory", - "address": "0x3D696c38Dd958e635f9077e65b64aA9cf7c92627", + "address": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticAggregationIsmFactory" }, { - "name": "StaticMerkleRootMultisigIsmFactory", - "address": "0xA59Ba0A8D4ea5A5DC9c8B0101ba7E6eE6C3399A4", + "address": "0x7fFe8C9c17F46F94D784E148FbadD4bF66477722", "constructorArguments": "", - "isProxy": false + "isProxy": true, + "name": "StaticAggregationIsm" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x97B07A8BCc0d212A4C76679b303A4C6441BB6186" + "address": "0x6966b0E55883d49BFB24539356a2f8A673E02039", + "constructorArguments": "", + "isProxy": false, + "name": "StaticAggregationHookFactory" }, { - "name": "StaticMessageIdMultisigIsmFactory", - "address": "0x8f919348F9C4619A196Acb5e377f49E5E2C0B569", + "address": "0x4863236F3a05A1A1F0850fF8cd09afeBAE82d953", "constructorArguments": "", - "isProxy": false + "isProxy": true, + "name": "StaticAggregationHook" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0xb1Fc72f251A6b6c37CD4Ef8f9287578BCc7CE9Eb" + "address": "0x54148470292C24345fb828B003461a9444414517", + "constructorArguments": "", + "isProxy": false, + "name": "DomainRoutingIsmFactory" }, { - "name": "StaticAggregationIsmFactory", - "address": "0x0048FaB53526D9a0478f66D660059E3E3611FE3E", + "address": "0x10c9FF6EEE4BaD29734322467f541C84001422C2", + "constructorArguments": "", + "isProxy": true, + "name": "DomaingRoutingIsm" + } + ], + "scrollsepolia": [ + { + "address": "0x6E7b29CB2A7617405B4d30C6f84bBD51b4Bb4be8", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticMerkleRootMultisigIsmFactory" }, { - "name": "StaticAggregationIsm", - "address": "0x13b6Cb8d51C7e15e5fFee432CBB1d4614972A87e" + "address": "0x209e7F9d40954E230008B9bb076a0901d32695e5", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticAggregationHookFactory", - "address": "0x00DFB81Bfc45fa03060b605273147F274ea807E5", + "address": "0x275aCcCa81cAD931dC6fB6E49ED233Bc99Bed4A7", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticMerkleRootMultisigIsmFactory" }, { - "name": "StaticAggregationHook", - "address": "0x713496A1477a08230E526a4e9449FCCE4d6523e9" + "address": "0x33999AB153F68D481AAB1B238368Ffd1Fe81F360", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "DomainRoutingIsmFactory", - "address": "0x385C7f179168f5Da92c72E17AE8EF50F3874077f", + "address": "0xeb6f11189197223c656807a83B0DD374f9A6dF44", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticMessageIdMultisigIsmFactory" }, { - "name": "DomaingRoutingIsm", - "address": "0xCd76aC44337BbA003bb4Bed2Ac37604148F36bA9" + "address": "0x3e6F45B03314bD21BcE4201666d483291575E391", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x97B07A8BCc0d212A4C76679b303A4C6441BB6186" + "address": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", + "constructorArguments": "", + "isProxy": false, + "name": "StaticAggregationIsmFactory" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0xb1Fc72f251A6b6c37CD4Ef8f9287578BCc7CE9Eb" + "address": "0x87935eB971eaA9826060261b07a919451dfd0409", + "name": "StaticAggregationIsm" }, { - "name": "StaticAggregationIsm", - "address": "0x13b6Cb8d51C7e15e5fFee432CBB1d4614972A87e" + "address": "0x44b764045BfDC68517e10e783E69B376cef196B2", + "constructorArguments": "", + "isProxy": false, + "name": "StaticAggregationHookFactory" }, { - "name": "StaticAggregationHook", - "address": "0x713496A1477a08230E526a4e9449FCCE4d6523e9" + "address": "0xE5cA56294dA5Bd490D5Bc489B177B002ad16AF83", + "name": "StaticAggregationHook" }, { - "name": "DomaingRoutingIsm", - "address": "0xCd76aC44337BbA003bb4Bed2Ac37604148F36bA9" + "address": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", + "constructorArguments": "", + "isProxy": false, + "name": "DomainRoutingIsmFactory" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x97B07A8BCc0d212A4C76679b303A4C6441BB6186" + "address": "0x7fFe8C9c17F46F94D784E148FbadD4bF66477722", + "name": "DomaingRoutingIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0xb1Fc72f251A6b6c37CD4Ef8f9287578BCc7CE9Eb" + "address": "0x33999AB153F68D481AAB1B238368Ffd1Fe81F360", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticAggregationIsm", - "address": "0x13b6Cb8d51C7e15e5fFee432CBB1d4614972A87e" + "address": "0x3e6F45B03314bD21BcE4201666d483291575E391", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationHook", - "address": "0x713496A1477a08230E526a4e9449FCCE4d6523e9" + "address": "0x87935eB971eaA9826060261b07a919451dfd0409", + "name": "StaticAggregationIsm" }, { - "name": "DomaingRoutingIsm", - "address": "0xCd76aC44337BbA003bb4Bed2Ac37604148F36bA9" + "address": "0xE5cA56294dA5Bd490D5Bc489B177B002ad16AF83", + "name": "StaticAggregationHook" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x97B07A8BCc0d212A4C76679b303A4C6441BB6186" + "address": "0x7fFe8C9c17F46F94D784E148FbadD4bF66477722", + "name": "DomaingRoutingIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0xb1Fc72f251A6b6c37CD4Ef8f9287578BCc7CE9Eb" + "address": "0x33999AB153F68D481AAB1B238368Ffd1Fe81F360", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticAggregationIsm", - "address": "0x13b6Cb8d51C7e15e5fFee432CBB1d4614972A87e" + "address": "0x3e6F45B03314bD21BcE4201666d483291575E391", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationHook", - "address": "0x713496A1477a08230E526a4e9449FCCE4d6523e9" + "address": "0x87935eB971eaA9826060261b07a919451dfd0409", + "name": "StaticAggregationIsm" }, { - "name": "DomaingRoutingIsm", - "address": "0xCd76aC44337BbA003bb4Bed2Ac37604148F36bA9" + "address": "0xE5cA56294dA5Bd490D5Bc489B177B002ad16AF83", + "name": "StaticAggregationHook" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x97B07A8BCc0d212A4C76679b303A4C6441BB6186" + "address": "0x7fFe8C9c17F46F94D784E148FbadD4bF66477722", + "name": "DomaingRoutingIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0xb1Fc72f251A6b6c37CD4Ef8f9287578BCc7CE9Eb" + "address": "0x33999AB153F68D481AAB1B238368Ffd1Fe81F360", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticAggregationIsm", - "address": "0x13b6Cb8d51C7e15e5fFee432CBB1d4614972A87e" + "address": "0x3e6F45B03314bD21BcE4201666d483291575E391", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationHook", - "address": "0x713496A1477a08230E526a4e9449FCCE4d6523e9" + "address": "0x87935eB971eaA9826060261b07a919451dfd0409", + "name": "StaticAggregationIsm" }, { - "name": "DomaingRoutingIsm", - "address": "0xCd76aC44337BbA003bb4Bed2Ac37604148F36bA9" + "address": "0xE5cA56294dA5Bd490D5Bc489B177B002ad16AF83", + "name": "StaticAggregationHook" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x97B07A8BCc0d212A4C76679b303A4C6441BB6186", - "constructorArguments": "", - "isProxy": true + "address": "0x7fFe8C9c17F46F94D784E148FbadD4bF66477722", + "name": "DomaingRoutingIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0xb1Fc72f251A6b6c37CD4Ef8f9287578BCc7CE9Eb", - "constructorArguments": "", - "isProxy": true + "address": "0x33999AB153F68D481AAB1B238368Ffd1Fe81F360", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticAggregationIsm", - "address": "0x13b6Cb8d51C7e15e5fFee432CBB1d4614972A87e", - "constructorArguments": "", - "isProxy": true + "address": "0x3e6F45B03314bD21BcE4201666d483291575E391", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationHook", - "address": "0x713496A1477a08230E526a4e9449FCCE4d6523e9", - "constructorArguments": "", - "isProxy": true + "address": "0x87935eB971eaA9826060261b07a919451dfd0409", + "name": "StaticAggregationIsm" }, { - "name": "DomainRoutingIsmFactory", - "address": "0xE8F752e5C4E1A6a2e3eAfa42d44D601A22d78f2b", - "constructorArguments": "", - "isProxy": false + "address": "0xE5cA56294dA5Bd490D5Bc489B177B002ad16AF83", + "name": "StaticAggregationHook" }, { - "name": "DomaingRoutingIsm", - "address": "0x063C2D908EAb532Cd207F309F0fd176ae6b2e1d1", - "constructorArguments": "", - "isProxy": true - } - ], - "scrollsepolia": [ + "address": "0x7fFe8C9c17F46F94D784E148FbadD4bF66477722", + "name": "DomaingRoutingIsm" + }, { - "name": "StaticMerkleRootMultisigIsmFactory", - "address": "0x6E7b29CB2A7617405B4d30C6f84bBD51b4Bb4be8", + "address": "0x33999AB153F68D481AAB1B238368Ffd1Fe81F360", "constructorArguments": "", - "isProxy": false + "isProxy": true, + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x209e7F9d40954E230008B9bb076a0901d32695e5" + "address": "0x3e6F45B03314bD21BcE4201666d483291575E391", + "constructorArguments": "", + "isProxy": true, + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticMerkleRootMultisigIsmFactory", - "address": "0x275aCcCa81cAD931dC6fB6E49ED233Bc99Bed4A7", + "address": "0x87935eB971eaA9826060261b07a919451dfd0409", "constructorArguments": "", - "isProxy": false + "isProxy": true, + "name": "StaticAggregationIsm" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x33999AB153F68D481AAB1B238368Ffd1Fe81F360" + "address": "0xE5cA56294dA5Bd490D5Bc489B177B002ad16AF83", + "constructorArguments": "", + "isProxy": true, + "name": "StaticAggregationHook" }, { - "name": "StaticMessageIdMultisigIsmFactory", - "address": "0xeb6f11189197223c656807a83B0DD374f9A6dF44", + "address": "0x17866ebE0e503784a9461d3e753dEeD0d3F61153", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "DomainRoutingIsmFactory" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0x3e6F45B03314bD21BcE4201666d483291575E391" - }, + "address": "0xea80345322520d37770dbDeD3FE9c53ba93E70D8", + "constructorArguments": "", + "isProxy": true, + "name": "DomaingRoutingIsm" + } + ], + "sepolia": [ { - "name": "StaticAggregationIsmFactory", - "address": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", + "address": "0xD356C996277eFb7f75Ee8bd61b31cC781A12F54f", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticMultisigIsmFactory" }, { - "name": "StaticAggregationIsm", - "address": "0x87935eB971eaA9826060261b07a919451dfd0409" + "address": "0x01812D60958798695391dacF092BAc4a715B1718", + "constructorArguments": "", + "isProxy": false, + "name": "StaticAggregationIsmFactory" }, { - "name": "StaticAggregationHookFactory", - "address": "0x44b764045BfDC68517e10e783E69B376cef196B2", + "address": "0xE67CfA164cDa449Ae38a0a09391eF6bCDf8e4e2c", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "DomainRoutingIsmFactory" }, { - "name": "StaticAggregationHook", - "address": "0xE5cA56294dA5Bd490D5Bc489B177B002ad16AF83" + "address": "0xA9999B4abC373FF2BB95B8725FABC96CA883d811", + "constructorArguments": "", + "isProxy": false, + "name": "StaticMerkleRootMultisigIsmFactory" }, { - "name": "DomainRoutingIsmFactory", - "address": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", + "address": "0xCCC126d96efcc342BF2781A7d224D3AB1F25B19C", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticMessageIdMultisigIsmFactory" }, { - "name": "DomaingRoutingIsm", - "address": "0x7fFe8C9c17F46F94D784E148FbadD4bF66477722" + "address": "0x0a71AcC99967829eE305a285750017C4916Ca269", + "constructorArguments": "", + "isProxy": false, + "name": "StaticMerkleRootMultisigIsmFactory" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x33999AB153F68D481AAB1B238368Ffd1Fe81F360" + "address": "0xbe94Cd5d0973dB4A6E63Cb9e6b2b2042D99F9Bcd", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0x3e6F45B03314bD21BcE4201666d483291575E391" + "address": "0xFEb9585b2f948c1eD74034205a7439261a9d27DD", + "constructorArguments": "", + "isProxy": false, + "name": "StaticMessageIdMultisigIsmFactory" }, { - "name": "StaticAggregationIsm", - "address": "0x87935eB971eaA9826060261b07a919451dfd0409" + "address": "0x218C5DFb7C2bC1e051FCFebBa8C6D66Dda28617E", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationHook", - "address": "0xE5cA56294dA5Bd490D5Bc489B177B002ad16AF83" + "address": "0xC83e12EF2627ACE445C298e6eC418684918a6002", + "constructorArguments": "", + "isProxy": false, + "name": "StaticAggregationIsmFactory" }, { - "name": "DomaingRoutingIsm", - "address": "0x7fFe8C9c17F46F94D784E148FbadD4bF66477722" + "address": "0x3dD1aB6B71EBfEd239869365FE5B8b47E0507d89", + "name": "StaticAggregationIsm" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x33999AB153F68D481AAB1B238368Ffd1Fe81F360" + "address": "0x160C28C92cA453570aD7C031972b58d5Dd128F72", + "constructorArguments": "", + "isProxy": false, + "name": "StaticAggregationHookFactory" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0x3e6F45B03314bD21BcE4201666d483291575E391" + "address": "0x4BC580FF77Dd1Eb320E96D34FddC97F0F04D3feD", + "name": "StaticAggregationHook" }, { - "name": "StaticAggregationIsm", - "address": "0x87935eB971eaA9826060261b07a919451dfd0409" + "address": "0x3603458990BfEb30f99E61B58427d196814D8ce1", + "constructorArguments": "", + "isProxy": false, + "name": "DomainRoutingIsmFactory" }, { - "name": "StaticAggregationHook", - "address": "0xE5cA56294dA5Bd490D5Bc489B177B002ad16AF83" + "address": "0x67543F48DBe592F1A15FD9aE37b1AbD104c70cf3", + "name": "DomaingRoutingIsm" }, { - "name": "DomaingRoutingIsm", - "address": "0x7fFe8C9c17F46F94D784E148FbadD4bF66477722" + "address": "0xbe94Cd5d0973dB4A6E63Cb9e6b2b2042D99F9Bcd", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x33999AB153F68D481AAB1B238368Ffd1Fe81F360" + "address": "0x218C5DFb7C2bC1e051FCFebBa8C6D66Dda28617E", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0x3e6F45B03314bD21BcE4201666d483291575E391" + "address": "0x3dD1aB6B71EBfEd239869365FE5B8b47E0507d89", + "name": "StaticAggregationIsm" }, { - "name": "StaticAggregationIsm", - "address": "0x87935eB971eaA9826060261b07a919451dfd0409" + "address": "0x4BC580FF77Dd1Eb320E96D34FddC97F0F04D3feD", + "name": "StaticAggregationHook" }, { - "name": "StaticAggregationHook", - "address": "0xE5cA56294dA5Bd490D5Bc489B177B002ad16AF83" + "address": "0x67543F48DBe592F1A15FD9aE37b1AbD104c70cf3", + "name": "DomaingRoutingIsm" }, { - "name": "DomaingRoutingIsm", - "address": "0x7fFe8C9c17F46F94D784E148FbadD4bF66477722" + "address": "0xbe94Cd5d0973dB4A6E63Cb9e6b2b2042D99F9Bcd", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x33999AB153F68D481AAB1B238368Ffd1Fe81F360" + "address": "0x218C5DFb7C2bC1e051FCFebBa8C6D66Dda28617E", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0x3e6F45B03314bD21BcE4201666d483291575E391" + "address": "0x3dD1aB6B71EBfEd239869365FE5B8b47E0507d89", + "name": "StaticAggregationIsm" }, { - "name": "StaticAggregationIsm", - "address": "0x87935eB971eaA9826060261b07a919451dfd0409" + "address": "0x4BC580FF77Dd1Eb320E96D34FddC97F0F04D3feD", + "name": "StaticAggregationHook" }, { - "name": "StaticAggregationHook", - "address": "0xE5cA56294dA5Bd490D5Bc489B177B002ad16AF83" + "address": "0x67543F48DBe592F1A15FD9aE37b1AbD104c70cf3", + "name": "DomaingRoutingIsm" }, { - "name": "DomaingRoutingIsm", - "address": "0x7fFe8C9c17F46F94D784E148FbadD4bF66477722" + "address": "0xbe94Cd5d0973dB4A6E63Cb9e6b2b2042D99F9Bcd", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x33999AB153F68D481AAB1B238368Ffd1Fe81F360", - "constructorArguments": "", - "isProxy": true + "address": "0x218C5DFb7C2bC1e051FCFebBa8C6D66Dda28617E", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0x3e6F45B03314bD21BcE4201666d483291575E391", - "constructorArguments": "", - "isProxy": true + "address": "0x3dD1aB6B71EBfEd239869365FE5B8b47E0507d89", + "name": "StaticAggregationIsm" }, { - "name": "StaticAggregationIsm", - "address": "0x87935eB971eaA9826060261b07a919451dfd0409", - "constructorArguments": "", - "isProxy": true + "address": "0x4BC580FF77Dd1Eb320E96D34FddC97F0F04D3feD", + "name": "StaticAggregationHook" }, { - "name": "StaticAggregationHook", - "address": "0xE5cA56294dA5Bd490D5Bc489B177B002ad16AF83", - "constructorArguments": "", - "isProxy": true + "address": "0x67543F48DBe592F1A15FD9aE37b1AbD104c70cf3", + "name": "DomaingRoutingIsm" }, { - "name": "DomainRoutingIsmFactory", - "address": "0x17866ebE0e503784a9461d3e753dEeD0d3F61153", - "constructorArguments": "", - "isProxy": false + "address": "0xbe94Cd5d0973dB4A6E63Cb9e6b2b2042D99F9Bcd", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "DomaingRoutingIsm", - "address": "0xea80345322520d37770dbDeD3FE9c53ba93E70D8", - "constructorArguments": "", - "isProxy": true - } - ], - "plumetestnet": [ - { - "name": "StaticMerkleRootMultisigIsmFactory", - "address": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", - "constructorArguments": "", - "isProxy": false + "address": "0x218C5DFb7C2bC1e051FCFebBa8C6D66Dda28617E", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x87935eB971eaA9826060261b07a919451dfd0409", - "constructorArguments": "", - "isProxy": true + "address": "0x3dD1aB6B71EBfEd239869365FE5B8b47E0507d89", + "name": "StaticAggregationIsm" }, { - "name": "StaticMessageIdMultisigIsmFactory", - "address": "0x44b764045BfDC68517e10e783E69B376cef196B2", - "constructorArguments": "", - "isProxy": false + "address": "0x4BC580FF77Dd1Eb320E96D34FddC97F0F04D3feD", + "name": "StaticAggregationHook" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0xE5cA56294dA5Bd490D5Bc489B177B002ad16AF83", - "constructorArguments": "", - "isProxy": true + "address": "0x67543F48DBe592F1A15FD9aE37b1AbD104c70cf3", + "name": "DomaingRoutingIsm" }, { - "name": "StaticAggregationIsmFactory", - "address": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", + "address": "0xbe94Cd5d0973dB4A6E63Cb9e6b2b2042D99F9Bcd", "constructorArguments": "", - "isProxy": false + "isProxy": true, + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticAggregationIsm", - "address": "0x7fFe8C9c17F46F94D784E148FbadD4bF66477722", + "address": "0x218C5DFb7C2bC1e051FCFebBa8C6D66Dda28617E", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationHookFactory", - "address": "0x6966b0E55883d49BFB24539356a2f8A673E02039", + "address": "0x3dD1aB6B71EBfEd239869365FE5B8b47E0507d89", "constructorArguments": "", - "isProxy": false + "isProxy": true, + "name": "StaticAggregationIsm" }, { - "name": "StaticAggregationHook", - "address": "0x4863236F3a05A1A1F0850fF8cd09afeBAE82d953", + "address": "0x4BC580FF77Dd1Eb320E96D34FddC97F0F04D3feD", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticAggregationHook" }, { - "name": "DomainRoutingIsmFactory", - "address": "0x54148470292C24345fb828B003461a9444414517", + "address": "0x3F100cBBE5FD5466BdB4B3a15Ac226957e7965Ad", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "DomainRoutingIsmFactory" }, { - "name": "DomaingRoutingIsm", - "address": "0x10c9FF6EEE4BaD29734322467f541C84001422C2", + "address": "0x9b0CC3BD9030CE269EF3124Bb36Cf954a490688e", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "DomaingRoutingIsm" } ] } diff --git a/typescript/infra/config/environments/testnet4/middleware.ts b/typescript/infra/config/environments/testnet4/middleware.ts index 607b4a3f6..cf3d2782c 100644 --- a/typescript/infra/config/environments/testnet4/middleware.ts +++ b/typescript/infra/config/environments/testnet4/middleware.ts @@ -1,5 +1,3 @@ -import { RpcConsensusType } from '@hyperlane-xyz/sdk'; - import { LiquidityLayerRelayerConfig } from '../../../src/config/middleware.js'; import { environment } from './chains.js'; @@ -12,5 +10,4 @@ export const liquidityLayerRelayerConfig: LiquidityLayerRelayerConfig = { namespace: environment, prometheusPushGateway: 'http://prometheus-prometheus-pushgateway.monitoring.svc.cluster.local:9091', - connectionType: RpcConsensusType.Single, }; diff --git a/typescript/infra/config/environments/testnet4/supportedChainNames.ts b/typescript/infra/config/environments/testnet4/supportedChainNames.ts index c65393e55..c966447a1 100644 --- a/typescript/infra/config/environments/testnet4/supportedChainNames.ts +++ b/typescript/infra/config/environments/testnet4/supportedChainNames.ts @@ -4,6 +4,7 @@ export const supportedChainNames = [ 'alfajores', 'bsctestnet', 'eclipsetestnet', + 'holesky', 'fuji', 'plumetestnet', 'scrollsepolia', diff --git a/typescript/infra/config/environments/testnet4/validators.ts b/typescript/infra/config/environments/testnet4/validators.ts index 6e594ffa7..965f0f38b 100644 --- a/typescript/infra/config/environments/testnet4/validators.ts +++ b/typescript/infra/config/environments/testnet4/validators.ts @@ -70,6 +70,21 @@ export const validatorChainConfig = ( 'bsctestnet', ), }, + holesky: { + interval: 13, + reorgPeriod: getReorgPeriod('holesky'), + validators: validatorsConfig( + { + [Contexts.Hyperlane]: ['0x7ab28ad88bb45867137ea823af88e2cb02359c03'], + [Contexts.ReleaseCandidate]: [ + '0x7ab28ad88bb45867137ea823af88e2cb02359c03', + ], + [Contexts.Neutron]: [], + }, + 'holesky', + ), + }, + scrollsepolia: { interval: 5, reorgPeriod: getReorgPeriod('scrollsepolia'), diff --git a/typescript/infra/config/registry.ts b/typescript/infra/config/registry.ts index a85488014..3b05cf47e 100644 --- a/typescript/infra/config/registry.ts +++ b/typescript/infra/config/registry.ts @@ -1,7 +1,11 @@ import { dirname, join } from 'path'; import { fileURLToPath } from 'url'; -import { ChainAddresses } from '@hyperlane-xyz/registry'; +import { + ChainAddresses, + MergedRegistry, + PartialRegistry, +} from '@hyperlane-xyz/registry'; import { FileSystemRegistry } from '@hyperlane-xyz/registry/fs'; import { ChainMap, @@ -111,3 +115,26 @@ export function getMainnetAddresses(): ChainMap { export function getTestnetAddresses(): ChainMap { return getEnvAddresses('testnet4'); } + +/** + * Gets a registry, applying the provided overrides. The base registry + * that the overrides are applied to is the registry returned by `getRegistry`. + * @param chainMetadataOverrides Chain metadata overrides. + * @param chainAddressesOverrides Chain address overrides. + * @returns A MergedRegistry merging the registry from `getRegistry` and the overrides. + */ +export function getRegistryWithOverrides( + chainMetadataOverrides: ChainMap> = {}, + chainAddressesOverrides: ChainMap> = {}, +): MergedRegistry { + const baseRegistry = getRegistry(); + + const overrideRegistry = new PartialRegistry({ + chainMetadata: chainMetadataOverrides, + chainAddresses: chainAddressesOverrides, + }); + + return new MergedRegistry({ + registries: [baseRegistry, overrideRegistry], + }); +} diff --git a/typescript/infra/helm/helloworld-kathy/templates/_helpers.tpl b/typescript/infra/helm/helloworld-kathy/templates/_helpers.tpl index 12b1e0db9..286fd126a 100644 --- a/typescript/infra/helm/helloworld-kathy/templates/_helpers.tpl +++ b/typescript/infra/helm/helloworld-kathy/templates/_helpers.tpl @@ -91,10 +91,6 @@ The helloworld-kathy container {{- if .Values.hyperlane.cycleOnce }} - --cycle-once {{- end }} -{{- if .Values.hyperlane.connectionType }} - - --connection-type - - {{ .Values.hyperlane.connectionType }} -{{- end }} {{- if .Values.hyperlane.cyclesBetweenEthereumMessages }} - --cycles-between-ethereum-messages - "{{ .Values.hyperlane.cyclesBetweenEthereumMessages }}" diff --git a/typescript/infra/helm/helloworld-kathy/templates/external-secret.yaml b/typescript/infra/helm/helloworld-kathy/templates/external-secret.yaml index f0870da0a..115e249cd 100644 --- a/typescript/infra/helm/helloworld-kathy/templates/external-secret.yaml +++ b/typescript/infra/helm/helloworld-kathy/templates/external-secret.yaml @@ -33,7 +33,6 @@ spec: */}} {{- range .Values.hyperlane.chains }} GCP_SECRET_OVERRIDE_{{ $.Values.hyperlane.runEnv | upper }}_RPC_ENDPOINTS_{{ . | upper }}: {{ printf "'{{ .%s_rpcs | toString }}'" . }} - GCP_SECRET_OVERRIDE_{{ $.Values.hyperlane.runEnv | upper }}_RPC_ENDPOINT_{{ . | upper }}: {{ printf "'{{ .%s_rpc | toString }}'" . }} {{- end }} {{- if .Values.hyperlane.aws }} AWS_ACCESS_KEY_ID: {{ print "'{{ .aws_access_key_id | toString }}'" }} @@ -51,9 +50,6 @@ spec: - secretKey: {{ printf "%s_rpcs" . }} remoteRef: key: {{ printf "%s-rpc-endpoints-%s" $.Values.hyperlane.runEnv . }} - - secretKey: {{ printf "%s_rpc" . }} - remoteRef: - key: {{ printf "%s-rpc-endpoint-%s" $.Values.hyperlane.runEnv . }} {{- end }} {{- if .Values.hyperlane.aws }} - secretKey: aws_access_key_id diff --git a/typescript/infra/helm/key-funder/templates/cron-job.yaml b/typescript/infra/helm/key-funder/templates/cron-job.yaml index 610ebf8bb..b89122281 100644 --- a/typescript/infra/helm/key-funder/templates/cron-job.yaml +++ b/typescript/infra/helm/key-funder/templates/cron-job.yaml @@ -29,10 +29,6 @@ spec: - --contexts-and-roles - {{ $context }}={{ join "," $roles }} {{- end }} -{{- if .Values.hyperlane.connectionType }} - - --connection-type - - {{ .Values.hyperlane.connectionType }} -{{- end }} {{- range $chain, $balance := .Values.hyperlane.desiredBalancePerChain }} - --desired-balance-per-chain - {{ $chain }}={{ $balance }} diff --git a/typescript/infra/helm/key-funder/templates/env-var-external-secret.yaml b/typescript/infra/helm/key-funder/templates/env-var-external-secret.yaml index de6577bfa..2a95d627e 100644 --- a/typescript/infra/helm/key-funder/templates/env-var-external-secret.yaml +++ b/typescript/infra/helm/key-funder/templates/env-var-external-secret.yaml @@ -29,7 +29,6 @@ spec: */}} {{- range .Values.hyperlane.chains }} GCP_SECRET_OVERRIDE_{{ $.Values.hyperlane.runEnv | upper }}_RPC_ENDPOINTS_{{ . | upper }}: {{ printf "'{{ .%s_rpcs | toString }}'" . }} - GCP_SECRET_OVERRIDE_{{ $.Values.hyperlane.runEnv | upper }}_RPC_ENDPOINT_{{ . | upper }}: {{ printf "'{{ .%s_rpc | toString }}'" . }} {{- end }} data: - secretKey: deployer_key @@ -43,7 +42,4 @@ spec: - secretKey: {{ printf "%s_rpcs" . }} remoteRef: key: {{ printf "%s-rpc-endpoints-%s" $.Values.hyperlane.runEnv . }} - - secretKey: {{ printf "%s_rpc" . }} - remoteRef: - key: {{ printf "%s-rpc-endpoint-%s" $.Values.hyperlane.runEnv . }} {{- end }} diff --git a/typescript/infra/helm/liquidity-layer-relayers/templates/circle-relayer-deployment.yaml b/typescript/infra/helm/liquidity-layer-relayers/templates/circle-relayer-deployment.yaml index 02dc71378..a41589924 100644 --- a/typescript/infra/helm/liquidity-layer-relayers/templates/circle-relayer-deployment.yaml +++ b/typescript/infra/helm/liquidity-layer-relayers/templates/circle-relayer-deployment.yaml @@ -21,10 +21,6 @@ spec: - ./typescript/infra/scripts/middleware/circle-relayer.ts - -e - {{ .Values.hyperlane.runEnv }} -{{- if .Values.hyperlane.connectionType }} - - --connection-type - - {{ .Values.hyperlane.connectionType }} -{{- end }} envFrom: - secretRef: name: liquidity-layer-env-var-secret diff --git a/typescript/infra/helm/liquidity-layer-relayers/templates/env-var-external-secret.yaml b/typescript/infra/helm/liquidity-layer-relayers/templates/env-var-external-secret.yaml index a8d44b48c..62b311712 100644 --- a/typescript/infra/helm/liquidity-layer-relayers/templates/env-var-external-secret.yaml +++ b/typescript/infra/helm/liquidity-layer-relayers/templates/env-var-external-secret.yaml @@ -29,7 +29,6 @@ spec: */}} {{- range .Values.hyperlane.chains }} GCP_SECRET_OVERRIDE_{{ $.Values.hyperlane.runEnv | upper }}_RPC_ENDPOINTS_{{ . | upper }}: {{ printf "'{{ .%s_rpcs | toString }}'" . }} - GCP_SECRET_OVERRIDE_{{ $.Values.hyperlane.runEnv | upper }}_RPC_ENDPOINT_{{ . | upper }}: {{ printf "'{{ .%s_rpc | toString }}'" . }} {{- end }} data: - secretKey: deployer_key @@ -43,7 +42,4 @@ spec: - secretKey: {{ printf "%s_rpcs" . }} remoteRef: key: {{ printf "%s-rpc-endpoints-%s" $.Values.hyperlane.runEnv . }} - - secretKey: {{ printf "%s_rpc" . }} - remoteRef: - key: {{ printf "%s-rpc-endpoint-%s" $.Values.hyperlane.runEnv . }} {{- end }} diff --git a/typescript/infra/helm/liquidity-layer-relayers/templates/portal-relayer-deployment.yaml b/typescript/infra/helm/liquidity-layer-relayers/templates/portal-relayer-deployment.yaml index 8f1828424..933210d8d 100644 --- a/typescript/infra/helm/liquidity-layer-relayers/templates/portal-relayer-deployment.yaml +++ b/typescript/infra/helm/liquidity-layer-relayers/templates/portal-relayer-deployment.yaml @@ -21,10 +21,6 @@ spec: - ./typescript/infra/scripts/middleware/portal-relayer.ts - -e - {{ .Values.hyperlane.runEnv }} -{{- if .Values.hyperlane.connectionType }} - - --connection-type - - {{ .Values.hyperlane.connectionType }} -{{- end }} envFrom: - secretRef: name: liquidity-layer-env-var-secret diff --git a/typescript/infra/package.json b/typescript/infra/package.json index 7d97bf316..66ecf3131 100644 --- a/typescript/infra/package.json +++ b/typescript/infra/package.json @@ -12,6 +12,7 @@ "@ethersproject/experimental": "^5.7.0", "@ethersproject/hardware-wallets": "^5.7.0", "@ethersproject/providers": "^5.7.2", + "@google-cloud/secret-manager": "^5.5.0", "@hyperlane-xyz/helloworld": "4.0.0-alpha.2", "@hyperlane-xyz/registry": "1.3.0", "@hyperlane-xyz/sdk": "4.0.0-alpha.2", diff --git a/typescript/infra/scripts/agent-utils.ts b/typescript/infra/scripts/agent-utils.ts index d027d5aad..313af5d44 100644 --- a/typescript/infra/scripts/agent-utils.ts +++ b/typescript/infra/scripts/agent-utils.ts @@ -1,14 +1,14 @@ import path, { join } from 'path'; import yargs, { Argv } from 'yargs'; -import { ChainAddresses } from '@hyperlane-xyz/registry'; +import { ChainAddresses, IRegistry } from '@hyperlane-xyz/registry'; import { ChainMap, ChainMetadata, ChainName, CoreConfig, + MultiProtocolProvider, MultiProvider, - RpcConsensusType, collectValidators, } from '@hyperlane-xyz/sdk'; import { @@ -16,6 +16,7 @@ import { ProtocolType, objFilter, objMap, + objMerge, promiseObjAll, rootLogger, symmetricDifference, @@ -35,7 +36,10 @@ import { getCurrentKubernetesContext } from '../src/agents/index.js'; import { getCloudAgentKey } from '../src/agents/key-utils.js'; import { CloudAgentKey } from '../src/agents/keys.js'; import { RootAgentConfig } from '../src/config/agent/agent.js'; -import { fetchProvider } from '../src/config/chain.js'; +import { + fetchProvider, + getSecretMetadataOverrides, +} from '../src/config/chain.js'; import { AgentEnvironment, DeployEnvironment, @@ -47,6 +51,7 @@ import { assertContext, assertRole, getInfraPath, + inCIMode, readJSONAtPath, writeMergedJSONAtPath, } from '../src/utils/utils.js'; @@ -96,13 +101,6 @@ export function withModuleAndFork(args: Argv) { .alias('f', 'fork'); } -export function withNetwork(args: Argv) { - return args - .describe('network', 'network to target') - .choices('network', getChains()) - .alias('n', 'network'); -} - export function withContext(args: Argv) { return args .describe('context', 'deploy context') @@ -112,6 +110,17 @@ export function withContext(args: Argv) { .demandOption('context'); } +export function withChainRequired(args: Argv) { + return withChain(args).demandOption('chain'); +} + +export function withChain(args: Argv) { + return args + .describe('chain', 'chain name') + .choices('chain', getChains()) + .alias('c', 'chain'); +} + export function withProtocol(args: Argv) { return args .describe('protocol', 'protocol type') @@ -171,6 +180,17 @@ export function withConcurrentDeploy(args: Argv) { .default('concurrentDeploy', false); } +export function withRpcUrls(args: Argv) { + return args + .describe( + 'rpcUrls', + 'rpc urls in a comma separated list, in order of preference', + ) + .string('rpcUrls') + .demandOption('rpcUrls') + .alias('r', 'rpcUrls'); +} + // not requiring to build coreConfig to get agentConfig export async function getAgentConfigsBasedOnArgs(argv?: { environment: DeployEnvironment; @@ -283,8 +303,8 @@ export function ensureValidatorConfigConsistency(agentConfig: RootAgentConfig) { export function getKeyForRole( environment: DeployEnvironment, context: Contexts, - chain: ChainName, role: Role, + chain?: ChainName, index?: number, ): CloudAgentKey { debugLog(`Getting key for ${role} role`); @@ -292,33 +312,32 @@ export function getKeyForRole( return getCloudAgentKey(agentConfig, role, chain, index); } +export async function getMultiProtocolProvider( + registry: IRegistry, +): Promise { + const chainMetadata = await registry.getMetadata(); + return new MultiProtocolProvider(chainMetadata); +} + export async function getMultiProviderForRole( - txConfigs: ChainMap, environment: DeployEnvironment, + registry: IRegistry, context: Contexts, role: Role, index?: number, - // TODO: rename to consensusType? - connectionType?: RpcConsensusType, ): Promise { + const chainMetadata = await registry.getMetadata(); debugLog(`Getting multiprovider for ${role} role`); - const multiProvider = new MultiProvider(txConfigs); - if (process.env.CI === 'true') { - debugLog('Returning multiprovider with default RPCs in CI'); - // Return the multiProvider with default RPCs + const multiProvider = new MultiProvider(chainMetadata); + if (inCIMode()) { + debugLog('Running in CI, returning multiprovider without secret keys'); return multiProvider; } await promiseObjAll( - objMap(txConfigs, async (chain, _) => { + objMap(chainMetadata, async (chain, _) => { 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); + const key = getKeyForRole(environment, context, role, chain, index); + const signer = await key.getSigner(); multiProvider.setSigner(chain, signer); } }), @@ -330,23 +349,22 @@ export async function getMultiProviderForRole( // Note: this will only work for keystores that allow key's to be extracted. // I.e. GCP will work but AWS HSMs will not. export async function getKeysForRole( - txConfigs: ChainMap, environment: DeployEnvironment, + supportedChainNames: ChainName[], context: Contexts, role: Role, index?: number, ): Promise> { - if (process.env.CI === 'true') { + if (inCIMode()) { debugLog('No keys to return in CI'); return {}; } - const keys = await promiseObjAll( - objMap(txConfigs, async (chain, _) => - getKeyForRole(environment, context, chain, role, index), - ), - ); - return keys; + const keyEntries = supportedChainNames.map((chain) => [ + chain, + getKeyForRole(environment, context, role, chain, index), + ]); + return Object.fromEntries(keyEntries); } export function getEnvironmentDirectory(environment: DeployEnvironment) { diff --git a/typescript/infra/scripts/agents/update-agent-config.ts b/typescript/infra/scripts/agents/update-agent-config.ts index 2ec6431b8..6b0ed65fd 100644 --- a/typescript/infra/scripts/agents/update-agent-config.ts +++ b/typescript/infra/scripts/agents/update-agent-config.ts @@ -6,7 +6,12 @@ async function main() { const { environment } = await getArgs().argv; const envConfig = getEnvironmentConfig(environment); - let multiProvider = await envConfig.getMultiProvider(); + let multiProvider = await envConfig.getMultiProvider( + undefined, + undefined, + // Don't use secrets + false, + ); await writeAgentConfig(multiProvider, environment); } diff --git a/typescript/infra/scripts/check-rpc-urls.ts b/typescript/infra/scripts/check-rpc-urls.ts index 2ab70835b..307e4515c 100644 --- a/typescript/infra/scripts/check-rpc-urls.ts +++ b/typescript/infra/scripts/check-rpc-urls.ts @@ -2,25 +2,20 @@ import { ethers } from 'ethers'; import { rootLogger } from '@hyperlane-xyz/utils'; -import { getSecretRpcEndpoint } from '../src/agents/index.js'; +import { getSecretRpcEndpoints } from '../src/agents/index.js'; import { getArgs } from './agent-utils.js'; import { getEnvironmentConfig } from './core-utils.js'; -// 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); + const config = getEnvironmentConfig(environment); const multiProvider = await config.getMultiProvider(); const chains = multiProvider.getKnownChainNames(); const providers: [string, ethers.providers.JsonRpcProvider][] = []; for (const chain of chains) { rootLogger.debug(`Building providers for ${chain}`); - const rpcData = [ - ...(await getSecretRpcEndpoint(environment, chain, false)), - ...(await getSecretRpcEndpoint(environment, chain, true)), - ]; + const rpcData = await getSecretRpcEndpoints(environment, chain); for (const url of rpcData) providers.push([chain, new ethers.providers.StaticJsonRpcProvider(url)]); } diff --git a/typescript/infra/scripts/deploy.ts b/typescript/infra/scripts/deploy.ts index 73ce91679..4ecd59eed 100644 --- a/typescript/infra/scripts/deploy.ts +++ b/typescript/infra/scripts/deploy.ts @@ -40,10 +40,10 @@ import { getArgs, getModuleDirectory, withBuildArtifactPath, + withChain, withConcurrentDeploy, withContext, withModuleAndFork, - withNetwork, } from './agent-utils.js'; import { getEnvironmentConfig, getHyperlaneCore } from './core-utils.js'; @@ -53,12 +53,12 @@ async function main() { module, fork, environment, - network, + chain, buildArtifactPath, concurrentDeploy, } = await withContext( withConcurrentDeploy( - withNetwork(withModuleAndFork(withBuildArtifactPath(getArgs()))), + withChain(withModuleAndFork(withBuildArtifactPath(getArgs()))), ), ).argv; const envConfig = getEnvironmentConfig(environment); @@ -227,7 +227,7 @@ async function main() { // prompt for confirmation in production environments if (environment !== 'test' && !fork) { - const confirmConfig = network ? config[network] : config; + const confirmConfig = chain ? config[chain] : config; console.log(JSON.stringify(confirmConfig, null, 2)); const { value: confirmed } = await prompts({ type: 'confirm', @@ -244,7 +244,7 @@ async function main() { config, deployer, cache, - network ?? fork, + chain ?? fork, agentConfig, ); } diff --git a/typescript/infra/scripts/funding/fund-keys-from-deployer.ts b/typescript/infra/scripts/funding/fund-keys-from-deployer.ts index 02b15a978..b949b9a32 100644 --- a/typescript/infra/scripts/funding/fund-keys-from-deployer.ts +++ b/typescript/infra/scripts/funding/fund-keys-from-deployer.ts @@ -9,7 +9,6 @@ import { ChainName, HyperlaneIgp, MultiProvider, - RpcConsensusType, } from '@hyperlane-xyz/sdk'; import { Address, objFilter, objMap, rootLogger } from '@hyperlane-xyz/utils'; @@ -177,11 +176,6 @@ async function main() { ) .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) - .demandOption('connection-type') - .boolean('skip-igp-claim') .describe('skip-igp-claim', 'If true, never claims funds from the IGP') .default('skip-igp-claim', false).argv; @@ -191,7 +185,6 @@ async function main() { const multiProvider = await config.getMultiProvider( Contexts.Hyperlane, // Always fund from the hyperlane context Role.Deployer, // Always fund from the deployer - argv.connectionType, ); let contextFunders: ContextFunder[]; @@ -381,7 +374,7 @@ class ContextFunder { [Role.Kathy]: '', }; const roleKeysPerChain: ChainMap> = {}; - const chains = getEnvironmentConfig(environment).chainMetadataConfigs; + const { supportedChainNames } = getEnvironmentConfig(environment); for (const role of rolesToFund) { assertFundableRole(role); // only the relayer and kathy are fundable keys const roleAddress = fetchLocalKeyAddresses(role)[environment][context]; @@ -392,7 +385,7 @@ class ContextFunder { } fundableRoleKeys[role] = roleAddress; - for (const chain of Object.keys(chains)) { + for (const chain of supportedChainNames) { if (!roleKeysPerChain[chain as ChainName]) { roleKeysPerChain[chain as ChainName] = { [Role.Relayer]: [], diff --git a/typescript/infra/scripts/helloworld/kathy.ts b/typescript/infra/scripts/helloworld/kathy.ts index 7c212831e..cea226d4f 100644 --- a/typescript/infra/scripts/helloworld/kathy.ts +++ b/typescript/infra/scripts/helloworld/kathy.ts @@ -11,7 +11,6 @@ import { MultiProtocolCore, MultiProvider, ProviderType, - RpcConsensusType, TypedTransactionReceipt, } from '@hyperlane-xyz/sdk'; import { @@ -19,6 +18,7 @@ import { ProtocolType, ensure0x, objMap, + pick, retryAsync, rootLogger, sleep, @@ -27,7 +27,6 @@ import { } from '@hyperlane-xyz/utils'; import { Contexts } from '../../config/contexts.js'; -import { testnetConfigs } from '../../config/environments/testnet4/chains.js'; import { hyperlaneHelloworld, releaseCandidateHelloworld, @@ -127,16 +126,6 @@ function getKathyArgs() { chainStrs.map((chainStr: string) => assertChain(chainStr)), ) - .string('connection-type') - .describe('connection-type', 'The provider connection type to use for RPCs') - .default('connection-type', RpcConsensusType.Single) - .choices('connection-type', [ - RpcConsensusType.Single, - RpcConsensusType.Quorum, - RpcConsensusType.Fallback, - ]) - .demandOption('connection-type') - .number('cycles-between-ethereum-messages') .describe( 'cycles-between-ethereum-messages', @@ -155,7 +144,6 @@ async function main(): Promise { fullCycleTime, messageSendTimeout, messageReceiptTimeout, - connectionType, cyclesBetweenEthereumMessages, } = await getKathyArgs(); @@ -165,7 +153,6 @@ async function main(): Promise { logger.debug('Starting up', { environment }); const coreConfig = getEnvironmentConfig(environment); - // const coreConfig = getCoreConfigStub(environment); const { app, core, igp, multiProvider, keys } = await getHelloWorldMultiProtocolApp( @@ -173,7 +160,6 @@ async function main(): Promise { context, Role.Kathy, undefined, - connectionType, ); const appChains = app.chains(); @@ -544,7 +530,14 @@ async function updateWalletBalanceMetricFor( } // Get a core config intended for testing Kathy without secret access -export function getCoreConfigStub(environment: DeployEnvironment) { +export async function getCoreConfigStub(environment: DeployEnvironment) { + const environmentConfig = getEnvironmentConfig(environment); + // Don't fetch any secrets. + const registry = await environmentConfig.getRegistry(false); + const testnetConfigs = pick( + await registry.getMetadata(), + environmentConfig.supportedChainNames, + ); const multiProvider = new MultiProvider({ // Desired chains here. Key must have funds on these chains ...testnetConfigs, diff --git a/typescript/infra/scripts/helloworld/utils.ts b/typescript/infra/scripts/helloworld/utils.ts index 38ab8969f..03cb408eb 100644 --- a/typescript/infra/scripts/helloworld/utils.ts +++ b/typescript/infra/scripts/helloworld/utils.ts @@ -8,7 +8,6 @@ import { MultiProtocolCore, MultiProtocolProvider, MultiProvider, - RpcConsensusType, attachContractsMap, attachContractsMapAndGetForeignDeployments, filterChainMapToProtocol, @@ -28,12 +27,10 @@ export async function getHelloWorldApp( context: Contexts, keyRole: Role, keyContext: Contexts = context, - connectionType: RpcConsensusType = RpcConsensusType.Single, ) { const multiProvider: MultiProvider = await coreConfig.getMultiProvider( keyContext, keyRole, - connectionType, ); const helloworldConfig = getHelloWorldConfig(coreConfig, context); @@ -61,12 +58,10 @@ export async function getHelloWorldMultiProtocolApp( context: Contexts, keyRole: Role, keyContext: Contexts = context, - connectionType: RpcConsensusType = RpcConsensusType.Single, ) { const multiProvider: MultiProvider = await coreConfig.getMultiProvider( keyContext, keyRole, - connectionType, ); const envAddresses = getEnvAddresses(coreConfig.environment); diff --git a/typescript/infra/scripts/print-chain-metadatas.ts b/typescript/infra/scripts/print-chain-metadatas.ts index 8653ef908..ff8f771fe 100644 --- a/typescript/infra/scripts/print-chain-metadatas.ts +++ b/typescript/infra/scripts/print-chain-metadatas.ts @@ -1,3 +1,5 @@ +import { pick } from '@hyperlane-xyz/utils'; + import { getArgs } from './agent-utils.js'; import { getEnvironmentConfig } from './core-utils.js'; @@ -9,7 +11,15 @@ async function main() { const environmentConfig = getEnvironmentConfig(args.environment); - console.log(JSON.stringify(environmentConfig.chainMetadataConfigs, null, 2)); + // Intentionally do not include any secrets in the output + const registry = await environmentConfig.getRegistry(false); + const allMetadata = await registry.getMetadata(); + const environmentMetadata = pick( + allMetadata, + environmentConfig.supportedChainNames, + ); + + console.log(JSON.stringify(environmentMetadata, null, 2)); } main().catch((err) => { diff --git a/typescript/infra/scripts/print-gas-prices.ts b/typescript/infra/scripts/print-gas-prices.ts index e06f13421..61126c371 100644 --- a/typescript/infra/scripts/print-gas-prices.ts +++ b/typescript/infra/scripts/print-gas-prices.ts @@ -1,47 +1,67 @@ import { ethers } from 'ethers'; -import { MultiProtocolProvider, ProviderType } from '@hyperlane-xyz/sdk'; -import { objMap, promiseObjAll } from '@hyperlane-xyz/utils'; +import { + ChainMap, + MultiProtocolProvider, + ProviderType, +} from '@hyperlane-xyz/sdk'; -import { mainnetConfigs } from '../config/environments/mainnet3/chains.js'; -import { getCosmosChainGasPrice } from '../src/config/gas-oracle.js'; +import { + GasPriceConfig, + getCosmosChainGasPrice, +} from '../src/config/gas-oracle.js'; + +import { getEnvironmentConfig } from './core-utils.js'; async function main() { - const allMetadatas = mainnetConfigs; - - const mpp = new MultiProtocolProvider(allMetadatas); - - const prices = await promiseObjAll( - objMap(allMetadatas, async (chain, metadata) => { - const provider = mpp.getProvider(chain); - switch (provider.type) { - case ProviderType.EthersV5: { - const gasPrice = await provider.provider.getGasPrice(); - return { - amount: ethers.utils.formatUnits(gasPrice, 'gwei'), - decimals: 9, - }; - } - case ProviderType.CosmJsWasm: { - const { amount } = await getCosmosChainGasPrice(chain); - - return { - amount, - decimals: 1, - }; - } - case ProviderType.SolanaWeb3: - // TODO get a reasonable value - return '0.001'; - default: - throw new Error(`Unsupported provider type: ${provider.type}`); - } - }), + const environmentConfig = getEnvironmentConfig('mainnet3'); + + const mpp = await environmentConfig.getMultiProtocolProvider(); + + const prices: ChainMap = Object.fromEntries( + await Promise.all( + environmentConfig.supportedChainNames.map(async (chain) => [ + chain, + await getGasPrice(mpp, chain), + ]), + ), ); console.log(JSON.stringify(prices, null, 2)); } +async function getGasPrice( + mpp: MultiProtocolProvider, + chain: string, +): Promise { + const provider = mpp.getProvider(chain); + switch (provider.type) { + case ProviderType.EthersV5: { + const gasPrice = await provider.provider.getGasPrice(); + return { + amount: ethers.utils.formatUnits(gasPrice, 'gwei'), + decimals: 9, + }; + } + case ProviderType.CosmJsWasm: { + const { amount } = await getCosmosChainGasPrice(chain); + + return { + amount, + decimals: 1, + }; + } + case ProviderType.SolanaWeb3: + // TODO get a reasonable value + return { + amount: '0.001', + decimals: 9, + }; + default: + throw new Error(`Unsupported provider type: ${provider.type}`); + } +} + main() .then() .catch((err) => { diff --git a/typescript/infra/scripts/print-token-prices.ts b/typescript/infra/scripts/print-token-prices.ts index 35c30fb5b..b125bac95 100644 --- a/typescript/infra/scripts/print-token-prices.ts +++ b/typescript/infra/scripts/print-token-prices.ts @@ -1,11 +1,17 @@ -import { objMap } from '@hyperlane-xyz/utils'; +import { objMap, pick } from '@hyperlane-xyz/utils'; -import { mainnetConfigs } from '../config/environments/mainnet3/chains.js'; +import { getEnvironmentConfig } from './core-utils.js'; const CURRENCY = 'usd'; async function main() { - const metadata = mainnetConfigs; + const environmentConfig = getEnvironmentConfig('mainnet3'); + + const registry = await environmentConfig.getRegistry(); + const metadata = pick( + await registry.getMetadata(), + environmentConfig.supportedChainNames, + ); const ids = objMap( metadata, diff --git a/typescript/infra/scripts/verify.ts b/typescript/infra/scripts/verify.ts index 9c15c6597..0e1f782d4 100644 --- a/typescript/infra/scripts/verify.ts +++ b/typescript/infra/scripts/verify.ts @@ -12,12 +12,12 @@ import { } from '../src/deployment/verify.js'; import { readJSONAtPath } from '../src/utils/utils.js'; -import { getArgs, withBuildArtifactPath, withNetwork } from './agent-utils.js'; +import { getArgs, withBuildArtifactPath, withChain } from './agent-utils.js'; import { getEnvironmentConfig } from './core-utils.js'; async function main() { - const { environment, buildArtifactPath, verificationArtifactPath, network } = - await withNetwork(withBuildArtifactPath(getArgs())) + const { environment, buildArtifactPath, verificationArtifactPath, chain } = + await withChain(withBuildArtifactPath(getArgs())) .string('verificationArtifactPath') .describe( 'verificationArtifactPath', @@ -54,7 +54,7 @@ async function main() { // verify all the things const failedResults = ( - await verifier.verify(network ? [network] : undefined) + await verifier.verify(chain ? [chain] : undefined) ).filter((result) => result.status === 'rejected'); // only log the failed verifications to console diff --git a/typescript/infra/src/agents/index.ts b/typescript/infra/src/agents/index.ts index 41eabefae..c959bca65 100644 --- a/typescript/infra/src/agents/index.ts +++ b/typescript/infra/src/agents/index.ts @@ -17,7 +17,12 @@ import { ScraperConfigHelper } from '../config/agent/scraper.js'; import { ValidatorConfigHelper } from '../config/agent/validator.js'; import { DeployEnvironment } from '../config/environment.js'; import { AgentRole, Role } from '../roles.js'; -import { fetchGCPSecret } from '../utils/gcloud.js'; +import { + fetchGCPSecret, + gcpSecretExistsUsingClient, + getGcpSecretLatestVersionName, + setGCPSecretUsingClient, +} from '../utils/gcloud.js'; import { HelmCommand, buildHelmChartDependencies, @@ -287,6 +292,13 @@ export class ValidatorHelmManager extends MultichainAgentHelmManager { } } +export function getSecretName( + environment: string, + chainName: ChainName, +): string { + return `${environment}-rpc-endpoints-${chainName}`; +} + export async function getSecretAwsCredentials(agentConfig: AgentContextConfig) { return { accessKeyId: await fetchGCPSecret( @@ -300,20 +312,14 @@ export async function getSecretAwsCredentials(agentConfig: AgentContextConfig) { }; } -export async function getSecretRpcEndpoint( +export async function getSecretRpcEndpoints( environment: string, chainName: ChainName, - quorum = false, ): Promise { - const secret = await fetchGCPSecret( - `${environment}-rpc-endpoint${quorum ? 's' : ''}-${chainName}`, - quorum, - ); - if (typeof secret != 'string' && !Array.isArray(secret)) { - throw Error(`Expected secret for ${chainName} rpc endpoint`); - } + const secret = await fetchGCPSecret(getSecretName(environment, chainName)); + if (!Array.isArray(secret)) { - return [secret.trimEnd()]; + throw Error(`Expected secret for ${chainName} rpc endpoint`); } return secret.map((i) => { @@ -323,6 +329,29 @@ export async function getSecretRpcEndpoint( }); } +export async function getSecretRpcEndpointsLatestVersionName( + environment: string, + chainName: ChainName, +) { + return getGcpSecretLatestVersionName(getSecretName(environment, chainName)); +} + +export async function secretRpcEndpointsExist( + environment: string, + chainName: ChainName, +): Promise { + return gcpSecretExistsUsingClient(getSecretName(environment, chainName)); +} + +export async function setSecretRpcEndpoints( + environment: string, + chainName: ChainName, + endpoints: string, +) { + const secretName = getSecretName(environment, chainName); + await setGCPSecretUsingClient(secretName, endpoints); +} + export async function getSecretDeployerKey( environment: DeployEnvironment, context: Contexts, diff --git a/typescript/infra/src/config/chain.ts b/typescript/infra/src/config/chain.ts index b64035b2b..51584fc37 100644 --- a/typescript/infra/src/config/chain.ts +++ b/typescript/infra/src/config/chain.ts @@ -1,16 +1,19 @@ +import { SecretManagerServiceClient } from '@google-cloud/secret-manager'; import { providers } from 'ethers'; +import { IRegistry } from '@hyperlane-xyz/registry'; import { + ChainMap, ChainMetadata, ChainName, HyperlaneSmartProvider, ProviderRetryOptions, - RpcConsensusType, } from '@hyperlane-xyz/sdk'; -import { ProtocolType, objFilter } from '@hyperlane-xyz/utils'; +import { ProtocolType, objFilter, objMerge } from '@hyperlane-xyz/utils'; -import { getChain } from '../../config/registry.js'; -import { getSecretRpcEndpoint } from '../agents/index.js'; +import { getChain, getRegistryWithOverrides } from '../../config/registry.js'; +import { getSecretRpcEndpoints } from '../agents/index.js'; +import { inCIMode } from '../utils/utils.js'; import { DeployEnvironment } from './environment.js'; @@ -20,37 +23,24 @@ export const defaultRetry: ProviderRetryOptions = { }; export async function fetchProvider( - environment: DeployEnvironment, chainName: ChainName, - connectionType: RpcConsensusType = RpcConsensusType.Single, ): Promise { const chainMetadata = getChain(chainName); if (!chainMetadata) { throw Error(`Unsupported chain: ${chainName}`); } const chainId = chainMetadata.chainId; - const single = connectionType === RpcConsensusType.Single; let rpcData = chainMetadata.rpcUrls.map((url) => url.http); if (rpcData.length === 0) { - rpcData = await getSecretRpcEndpoint(environment, chainName, !single); + throw Error(`No RPC URLs found for chain: ${chainName}`); } - 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}`); - } + return new HyperlaneSmartProvider( + chainId, + rpcData.map((url) => ({ http: url })), + undefined, + defaultRetry, + ); } export function getChainMetadatas(chains: Array) { @@ -73,3 +63,69 @@ export function getChainMetadatas(chains: Array) { return { ethereumMetadatas, nonEthereumMetadatas }; } + +/** + * Gets the registry for the given environment, with optional overrides and + * the ability to get overrides from secrets. + * @param deployEnv The deploy environment. + * @param chains The chains to get metadata for. + * @param defaultChainMetadataOverrides The default chain metadata overrides. If + * secret overrides are used, the secret overrides will be merged with these and + * take precedence. + * @param useSecrets Whether to fetch metadata overrides from secrets. + * @returns A registry with overrides for the given environment. + */ +export async function getRegistryForEnvironment( + deployEnv: DeployEnvironment, + chains: ChainName[], + defaultChainMetadataOverrides: ChainMap> = {}, + useSecrets: boolean = true, +): Promise { + let overrides = defaultChainMetadataOverrides; + if (useSecrets && !inCIMode()) { + overrides = objMerge( + overrides, + await getSecretMetadataOverrides(deployEnv, chains), + ); + } + const registry = getRegistryWithOverrides(overrides); + return registry; +} + +/** + * Gets chain metadata overrides from GCP secrets. + * @param deployEnv The deploy environment. + * @param chains The chains to get metadata overrides for. + * @returns A partial chain metadata map with the secret overrides. + */ +export async function getSecretMetadataOverrides( + deployEnv: DeployEnvironment, + chains: string[], +): Promise>> { + const chainMetadataOverrides: ChainMap> = {}; + + const secretRpcUrls = await Promise.all( + chains.map(async (chain) => { + const rpcUrls = await getSecretRpcEndpoints(deployEnv, chain); + return { + chain, + rpcUrls, + }; + }), + ); + + for (const { chain, rpcUrls } of secretRpcUrls) { + if (rpcUrls.length === 0) { + throw Error(`No secret RPC URLs found for chain: ${chain}`); + } + // Need explicit casting here because Zod expects a non-empty array. + const metadataRpcUrls = rpcUrls.map((rpcUrl: string) => ({ + http: rpcUrl, + })) as ChainMetadata['rpcUrls']; + chainMetadataOverrides[chain] = { + rpcUrls: metadataRpcUrls, + }; + } + + return chainMetadataOverrides; +} diff --git a/typescript/infra/src/config/environment.ts b/typescript/infra/src/config/environment.ts index fbb9b74c7..5b6fa0350 100644 --- a/typescript/infra/src/config/environment.ts +++ b/typescript/infra/src/config/environment.ts @@ -1,3 +1,4 @@ +import { IRegistry } from '@hyperlane-xyz/registry'; import { BridgeAdapterConfig, ChainMap, @@ -5,9 +6,9 @@ import { ChainName, CoreConfig, IgpConfig, + MultiProtocolProvider, MultiProvider, OwnableConfig, - RpcConsensusType, } from '@hyperlane-xyz/sdk'; import { objKeys } from '@hyperlane-xyz/utils'; @@ -39,17 +40,20 @@ export const envNameToAgentEnv: Record = { export type EnvironmentConfig = { environment: DeployEnvironment; - chainMetadataConfigs: ChainMap; + supportedChainNames: ChainName[]; + // Get the registry with or without environment-specific secrets. + getRegistry: (useSecrets?: boolean) => Promise; // Each AgentConfig, keyed by the context agents: Partial>; core: ChainMap; igp: ChainMap; owners: ChainMap; infra: InfrastructureConfig; + getMultiProtocolProvider: () => Promise; getMultiProvider: ( context?: Contexts, role?: Role, - connectionType?: RpcConsensusType, + useSecrets?: boolean, ) => Promise; getKeys: ( context?: Contexts, diff --git a/typescript/infra/src/config/funding.ts b/typescript/infra/src/config/funding.ts index a55a6524f..f75d19cfd 100644 --- a/typescript/infra/src/config/funding.ts +++ b/typescript/infra/src/config/funding.ts @@ -1,4 +1,4 @@ -import { ChainMap, RpcConsensusType } from '@hyperlane-xyz/sdk'; +import { ChainMap } from '@hyperlane-xyz/sdk'; import { Contexts } from '../../config/contexts.js'; import { FundableRole, Role } from '../roles.js'; @@ -20,7 +20,6 @@ export interface KeyFunderConfig { contextsAndRolesToFund: ContextAndRolesMap; cyclesBetweenEthereumMessages?: number; prometheusPushGateway: string; - connectionType: RpcConsensusType; desiredBalancePerChain: ChainMap; desiredKathyBalancePerChain: ChainMap; } diff --git a/typescript/infra/src/config/helloworld/types.ts b/typescript/infra/src/config/helloworld/types.ts index 1345894d8..ba2be0750 100644 --- a/typescript/infra/src/config/helloworld/types.ts +++ b/typescript/infra/src/config/helloworld/types.ts @@ -1,4 +1,4 @@ -import { ChainMap, ChainName, RpcConsensusType } from '@hyperlane-xyz/sdk'; +import { ChainMap, ChainName } from '@hyperlane-xyz/sdk'; import { DockerConfig } from '../agent/agent.js'; @@ -29,7 +29,6 @@ export interface HelloWorldKathyConfig { messageReceiptTimeout: number; // Which type of provider to use - connectionType: RpcConsensusType; // How many cycles to skip between a cycles that send messages to/from Ethereum. Defaults to 0. cyclesBetweenEthereumMessages?: number; } diff --git a/typescript/infra/src/config/middleware.ts b/typescript/infra/src/config/middleware.ts index e6f25f92b..19fc3d848 100644 --- a/typescript/infra/src/config/middleware.ts +++ b/typescript/infra/src/config/middleware.ts @@ -1,10 +1,7 @@ -import { RpcConsensusType } from '@hyperlane-xyz/sdk'; - import { DockerConfig } from './agent/agent.js'; export interface LiquidityLayerRelayerConfig { docker: DockerConfig; namespace: string; - connectionType: RpcConsensusType.Single | RpcConsensusType.Quorum; prometheusPushGateway: string; } diff --git a/typescript/infra/src/funding/key-funder.ts b/typescript/infra/src/funding/key-funder.ts index b149b6a0b..ae29c7842 100644 --- a/typescript/infra/src/funding/key-funder.ts +++ b/typescript/infra/src/funding/key-funder.ts @@ -48,7 +48,6 @@ function getKeyFunderHelmValues( chains: agentConfig.environmentChainNames, contextFundingFrom: keyFunderConfig.contextFundingFrom, contextsAndRolesToFund: keyFunderConfig.contextsAndRolesToFund, - connectionType: keyFunderConfig.connectionType, desiredBalancePerChain: keyFunderConfig.desiredBalancePerChain, desiredKathyBalancePerChain: keyFunderConfig.desiredKathyBalancePerChain, }, diff --git a/typescript/infra/src/helloworld/kathy.ts b/typescript/infra/src/helloworld/kathy.ts index 44651380d..6366049fa 100644 --- a/typescript/infra/src/helloworld/kathy.ts +++ b/typescript/infra/src/helloworld/kathy.ts @@ -85,7 +85,6 @@ function getHelloworldKathyHelmValues( messageReceiptTimeout: kathyConfig.messageReceiptTimeout, cycleOnce, fullCycleTime, - connectionType: kathyConfig.connectionType, cyclesBetweenEthereumMessages: kathyConfig.cyclesBetweenEthereumMessages, }, image: { diff --git a/typescript/infra/src/middleware/liquidity-layer-relayer.ts b/typescript/infra/src/middleware/liquidity-layer-relayer.ts index e334c921e..4a1c3d7a1 100644 --- a/typescript/infra/src/middleware/liquidity-layer-relayer.ts +++ b/typescript/infra/src/middleware/liquidity-layer-relayer.ts @@ -44,7 +44,6 @@ function getLiquidityLayerRelayerHelmValues( runEnv: agentConfig.runEnv, // Only used for fetching RPC urls as env vars chains: agentConfig.contextChainNames, - connectionType: relayerConfig.connectionType, }, image: { repository: relayerConfig.docker.repo, diff --git a/typescript/infra/src/utils/gcloud.ts b/typescript/infra/src/utils/gcloud.ts index 0ce5e21f3..56d913b7c 100644 --- a/typescript/infra/src/utils/gcloud.ts +++ b/typescript/infra/src/utils/gcloud.ts @@ -1,3 +1,4 @@ +import { SecretManagerServiceClient } from '@google-cloud/secret-manager'; import fs from 'fs'; import { rootLogger } from '@hyperlane-xyz/utils'; @@ -6,6 +7,8 @@ import { rm, writeFile } from 'fs/promises'; import { execCmd, execCmdAndParseJson } from './utils.js'; +export const GCP_PROJECT_ID = 'abacus-labs-dev'; + interface IamCondition { title: string; expression: string; @@ -32,9 +35,7 @@ export async function fetchGCPSecret( ); output = envVarOverride; } else { - [output] = await execCmd( - `gcloud secrets versions access latest --secret ${secretName}`, - ); + output = await fetchLatestGCPSecret(secretName); } if (parseJson) { @@ -43,6 +44,27 @@ export async function fetchGCPSecret( return output; } +export async function fetchLatestGCPSecret(secretName: string) { + const client = await getSecretManagerServiceClient(); + const [secretVersion] = await client.accessSecretVersion({ + name: `projects/${GCP_PROJECT_ID}/secrets/${secretName}/versions/latest`, + }); + const secretData = secretVersion.payload?.data; + if (!secretData) { + throw new Error(`Secret ${secretName} missing payload`); + } + + // Handle both string and Uint8Array + let dataStr: string; + if (typeof secretData === 'string') { + dataStr = secretData; + } else { + dataStr = new TextDecoder().decode(secretData); + } + + return dataStr; +} + // 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()}` @@ -60,6 +82,12 @@ function tryGCPSecretFromEnvVariable(gcpSecretName: string) { return process.env[overrideEnvVarName]; } +/** + * Checks if a secret exists in GCP using the gcloud CLI. + * @deprecated Use gcpSecretExistsUsingClient instead. + * @param secretName The name of the secret to check. + * @returns A boolean indicating whether the secret exists. + */ export async function gcpSecretExists(secretName: string) { const fullName = `projects/${await getCurrentProjectNumber()}/secrets/${secretName}`; debugLog(`Checking if GCP secret exists for ${fullName}`); @@ -71,6 +99,55 @@ export async function gcpSecretExists(secretName: string) { return matches.length > 0; } +/** + * Uses the SecretManagerServiceClient to check if a secret exists. + * @param secretName The name of the secret to check. + * @returns A boolean indicating whether the secret exists. + */ +export async function gcpSecretExistsUsingClient( + secretName: string, + client?: SecretManagerServiceClient, +): Promise { + if (!client) { + client = await getSecretManagerServiceClient(); + } + + try { + const fullSecretName = `projects/${await getCurrentProjectNumber()}/secrets/${secretName}`; + const [secrets] = await client.listSecrets({ + parent: `projects/${GCP_PROJECT_ID}`, + filter: `name=${fullSecretName}`, + }); + + return secrets.length > 0; + } catch (e) { + debugLog(`Error checking if secret exists: ${e}`); + throw e; + } +} + +export async function getGcpSecretLatestVersionName(secretName: string) { + const client = await getSecretManagerServiceClient(); + const [version] = await client.getSecretVersion({ + name: `projects/${GCP_PROJECT_ID}/secrets/${secretName}/versions/latest`, + }); + + return version?.name; +} + +export async function getSecretManagerServiceClient() { + return new SecretManagerServiceClient({ + projectId: GCP_PROJECT_ID, + }); +} + +/** + * Sets a GCP secret using the gcloud CLI. Create secret if it doesn't exist and add a new version or update the existing one. + * @deprecated Use setGCPSecretUsingClient instead. + * @param secretName The name of the secret to set. + * @param secret The secret to set. + * @param labels The labels to set on the secret. + */ export async function setGCPSecret( secretName: string, secret: string, @@ -97,6 +174,64 @@ export async function setGCPSecret( await rm(fileName); } +/** + * Sets a GCP secret using the SecretManagerServiceClient. Create secret if it doesn't exist and add a new version or update the existing one. + * @param secretName The name of the secret to set. + * @param secret The secret to set. + */ +export async function setGCPSecretUsingClient( + secretName: string, + secret: string, + labels?: Record, +) { + const client = await getSecretManagerServiceClient(); + + const exists = await gcpSecretExistsUsingClient(secretName, client); + if (!exists) { + // Create the secret + await client.createSecret({ + parent: `projects/${GCP_PROJECT_ID}`, + secretId: secretName, + secret: { + name: secretName, + replication: { + automatic: {}, + }, + labels, + }, + }); + debugLog(`Created new GCP secret for ${secretName}`); + } + await addGCPSecretVersion(secretName, secret, client); +} + +export async function addGCPSecretVersion( + secretName: string, + secret: string, + client?: SecretManagerServiceClient, +) { + if (!client) { + client = await getSecretManagerServiceClient(); + } + + const [version] = await client.addSecretVersion({ + parent: `projects/${GCP_PROJECT_ID}/secrets/${secretName}`, + payload: { + data: Buffer.from(secret, 'utf8'), + }, + }); + debugLog(`Added secret version ${version?.name}`); +} + +export async function disableGCPSecretVersion(secretName: string) { + const client = await getSecretManagerServiceClient(); + + const [version] = await client.disableSecretVersion({ + name: secretName, + }); + debugLog(`Disabled secret version ${version?.name}`); +} + // Returns the email of the service account export async function createServiceAccountIfNotExists( serviceAccountName: string, diff --git a/typescript/infra/src/utils/utils.ts b/typescript/infra/src/utils/utils.ts index 476fe3913..d3a8075ff 100644 --- a/typescript/infra/src/utils/utils.ts +++ b/typescript/infra/src/utils/utils.ts @@ -269,3 +269,7 @@ export function isEthereumProtocolChain(chainName: ChainName) { export function getInfraPath() { return join(dirname(fileURLToPath(import.meta.url)), '../../'); } + +export function inCIMode() { + return process.env.CI === 'true'; +} diff --git a/typescript/sdk/CHANGELOG.md b/typescript/sdk/CHANGELOG.md index f7b5bff47..245e84426 100644 --- a/typescript/sdk/CHANGELOG.md +++ b/typescript/sdk/CHANGELOG.md @@ -1,48 +1,12 @@ # @hyperlane-xyz/sdk -## 4.0.0-alpha.2 +## 3.14.0 ### Patch Changes -- 6b63c5d82: Adds deployment support for IsmConfig within a WarpRouteConfig - - @hyperlane-xyz/core@4.0.0-alpha.2 - - @hyperlane-xyz/utils@4.0.0-alpha.2 - -## 4.0.0-alpha.0 - -### Minor Changes - -- bdcbe1d16: Add EvmWarpModule with create() -- 6db9fa9ad: Implement hyperlane warp deploy - -### Patch Changes - -- @hyperlane-xyz/core@4.0.0-alpha.0 -- @hyperlane-xyz/utils@4.0.0-alpha.0 - -## 4.0.0-alpha - -### Major Changes - -- 74c879fa1: Merge branch 'cli-2.0' into main. - -### Minor Changes - -- eb23e7729: Add create() with EvmCoreModule -- 1ec61debd: Support hook config objects in warp config -- 4663018fc: Implement hyperlane core config to return CoreConfig -- 5e5886f2c: Add EvmIcaModule.create() -- 341b8affd: Completes the EvmIsmModule for creating, reading and updating ISMs. -- 3dabcbdca: Adds zod validation on tx submitter populated txs & re-uses ICA params for transformer. -- 1d0d1bb36: Implements `hyperlane core deploy` -- 4bf7301ea: Adds further zod schema validation support throughout the SDK, namely for /transactions. - -### Patch Changes - -- Updated dependencies [341b8affd] -- Updated dependencies [74c879fa1] - - @hyperlane-xyz/utils@4.0.0-alpha - - @hyperlane-xyz/core@4.0.0-alpha +- Updated dependencies [a8a68f6f6] + - @hyperlane-xyz/core@3.14.0 + - @hyperlane-xyz/utils@3.14.0 ## 3.13.0 diff --git a/typescript/sdk/src/consts/multisigIsm.ts b/typescript/sdk/src/consts/multisigIsm.ts index c6f8d8568..6dd1ba4c9 100644 --- a/typescript/sdk/src/consts/multisigIsm.ts +++ b/typescript/sdk/src/consts/multisigIsm.ts @@ -135,6 +135,11 @@ export const defaultMultisigConfigs: ChainMap = { ], }, + holesky: { + threshold: 1, + validators: ['0x7ab28ad88bb45867137ea823af88e2cb02359c03'], // TODO + }, + inevm: { threshold: 2, validators: [ diff --git a/typescript/sdk/src/metadata/agentConfig.ts b/typescript/sdk/src/metadata/agentConfig.ts index 0885a3e4a..49f05e883 100644 --- a/typescript/sdk/src/metadata/agentConfig.ts +++ b/typescript/sdk/src/metadata/agentConfig.ts @@ -399,6 +399,16 @@ export function buildAgentConfig( const chainConfigs: ChainMap = {}; for (const chain of [...chains].sort()) { const metadata = multiProvider.tryGetChainMetadata(chain); + if (metadata?.protocol === ProtocolType.Cosmos) { + // Note: the gRPC URL format in the registry lacks a correct http:// or https:// prefix at the moment, + // which is expected by the agents. For now, we intentionally skip this. + delete metadata.grpcUrls; + + // The agents expect gasPrice.amount and gasPrice.denom and ignore the transaction overrides. + // To reduce confusion when looking at the config, we remove the transaction overrides. + delete metadata.transactionOverrides; + } + const chainConfig: AgentChainMetadata = { ...metadata, ...addresses[chain], diff --git a/typescript/utils/CHANGELOG.md b/typescript/utils/CHANGELOG.md index 887e7681d..e0ab748ce 100644 --- a/typescript/utils/CHANGELOG.md +++ b/typescript/utils/CHANGELOG.md @@ -1,18 +1,6 @@ # @hyperlane-xyz/utils -## 4.0.0-alpha.2 - -## 4.0.0-alpha.0 - -## 4.0.0-alpha - -### Major Changes - -- 74c879fa1: Merge branch 'cli-2.0' into main. - -### Minor Changes - -- 341b8affd: Completes the EvmIsmModule for creating, reading and updating ISMs. +## 3.14.0 ## 3.13.0 diff --git a/yarn.lock b/yarn.lock index e8bf9eb8c..a5ec94b7a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4503,6 +4503,17 @@ __metadata: languageName: node linkType: hard +"@eslint/config-array@npm:^0.15.1": + version: 0.15.1 + resolution: "@eslint/config-array@npm:0.15.1" + dependencies: + "@eslint/object-schema": "npm:^2.1.3" + debug: "npm:^4.3.1" + minimatch: "npm:^3.0.5" + checksum: cf8f68a24498531180fad6846cb52dac4e852b0296d2664930bc15d6a2944ad427827bbaebfddf3f87b9c5db0e36c13974d6dc89fff8ba0d3d2b4357b8d52b4e + languageName: node + linkType: hard + "@eslint/eslintrc@npm:^2.1.4": version: 2.1.4 resolution: "@eslint/eslintrc@npm:2.1.4" @@ -4520,9 +4531,9 @@ __metadata: languageName: node linkType: hard -"@eslint/eslintrc@npm:^3.0.2": - version: 3.0.2 - resolution: "@eslint/eslintrc@npm:3.0.2" +"@eslint/eslintrc@npm:^3.1.0": + version: 3.1.0 + resolution: "@eslint/eslintrc@npm:3.1.0" dependencies: ajv: "npm:^6.12.4" debug: "npm:^4.3.2" @@ -4533,7 +4544,7 @@ __metadata: js-yaml: "npm:^4.1.0" minimatch: "npm:^3.1.2" strip-json-comments: "npm:^3.1.1" - checksum: 04e3d7de2b16fd59ba8985ecd6922eb488e630f94e4433858567a8a6c99b478bb7b47854b166b830b44905759547d0a03654eb1265952c812d5d1d70e3e4ccf9 + checksum: 02bf892d1397e1029209dea685e9f4f87baf643315df2a632b5f121ec7e8548a3b34f428a007234fa82772218fa8a3ac2d10328637b9ce63b7f8344035b74db3 languageName: node linkType: hard @@ -4544,10 +4555,17 @@ __metadata: languageName: node linkType: hard -"@eslint/js@npm:9.0.0": - version: 9.0.0 - resolution: "@eslint/js@npm:9.0.0" - checksum: b14b20af72410ef53e3e77e7d83cc1d6e6554b0092ceb9f969d25d765f4d775b4be32b0cd99bbfd6ce72eb2e4fb6b39b42a159b31909fbe1b3a5e88d75211687 +"@eslint/js@npm:9.4.0": + version: 9.4.0 + resolution: "@eslint/js@npm:9.4.0" + checksum: f1fa9acda8bab02dad21e9b7f46c6ba8cb3949979846caf7667f0c682ed0b56d9e8db143b00aab587ef2d02603df202eb5f7017d8f3a98be94be6efa763865ab + languageName: node + linkType: hard + +"@eslint/object-schema@npm:^2.1.3": + version: 2.1.4 + resolution: "@eslint/object-schema@npm:2.1.4" + checksum: 221e8d9f281c605948cd6e030874aacce83fe097f8f9c1964787037bccf08e82b7aa9eff1850a30fffac43f1d76555727ec22a2af479d91e268e89d1e035131e languageName: node linkType: hard @@ -5608,6 +5626,39 @@ __metadata: languageName: node linkType: hard +"@google-cloud/secret-manager@npm:^5.5.0": + version: 5.5.0 + resolution: "@google-cloud/secret-manager@npm:5.5.0" + dependencies: + google-gax: "npm:^4.0.3" + checksum: 487267dab1e260a0da79012194bb61c85f8b02b642330fdec32cac1fe37900f0fd6709ff4928fe631ab227b0d758bd3e59b1e3dc1d0682de566a64ef4fb42bba + languageName: node + linkType: hard + +"@grpc/grpc-js@npm:~1.10.3": + version: 1.10.8 + resolution: "@grpc/grpc-js@npm:1.10.8" + dependencies: + "@grpc/proto-loader": "npm:^0.7.13" + "@js-sdsl/ordered-map": "npm:^4.4.2" + checksum: cb7903e93db38a86bd2ddffb84313de78144454ad988801ede90f0c794d6a5f666a1b24f50e950b50d633b4bacc7416c7cabf4a6791b91c4fa89c001122edba8 + languageName: node + linkType: hard + +"@grpc/proto-loader@npm:^0.7.0, @grpc/proto-loader@npm:^0.7.13": + version: 0.7.13 + resolution: "@grpc/proto-loader@npm:0.7.13" + dependencies: + lodash.camelcase: "npm:^4.3.0" + long: "npm:^5.0.0" + protobufjs: "npm:^7.2.5" + yargs: "npm:^17.7.2" + bin: + proto-loader-gen-types: build/bin/proto-loader-gen-types.js + checksum: 7e2d842c2061cbaf6450c71da0077263be3bab165454d5c8a3e1ae4d3c6d2915f02fd27da63ff01f05e127b1221acd40705273f5d29303901e60514e852992f4 + languageName: node + linkType: hard + "@headlessui/react@npm:^1.7.17": version: 1.7.18 resolution: "@headlessui/react@npm:1.7.18" @@ -5632,17 +5683,6 @@ __metadata: languageName: node linkType: hard -"@humanwhocodes/config-array@npm:^0.12.3": - version: 0.12.3 - resolution: "@humanwhocodes/config-array@npm:0.12.3" - dependencies: - "@humanwhocodes/object-schema": "npm:^2.0.3" - debug: "npm:^4.3.1" - minimatch: "npm:^3.0.5" - checksum: b05f528c110aa1657d95d213e4ad2662f4161e838806af01a4d3f3b6ee3878d9b6f87d1b10704917f5c2f116757cb5c818480c32c4c4c6f84fe775a170b5f758 - languageName: node - linkType: hard - "@humanwhocodes/module-importer@npm:^1.0.1": version: 1.0.1 resolution: "@humanwhocodes/module-importer@npm:1.0.1" @@ -5657,10 +5697,10 @@ __metadata: languageName: node linkType: hard -"@humanwhocodes/object-schema@npm:^2.0.3": - version: 2.0.3 - resolution: "@humanwhocodes/object-schema@npm:2.0.3" - checksum: 05bb99ed06c16408a45a833f03a732f59bf6184795d4efadd33238ff8699190a8c871ad1121241bb6501589a9598dc83bf25b99dcbcf41e155cdf36e35e937a3 +"@humanwhocodes/retry@npm:^0.3.0": + version: 0.3.0 + resolution: "@humanwhocodes/retry@npm:0.3.0" + checksum: e574bab58680867414e225c9002e9a97eb396f85871c180fbb1a9bcdf9ded4b4de0b327f7d0c43b775873362b7c92956d4b322e8bc4b90be56077524341f04b2 languageName: node linkType: hard @@ -5786,6 +5826,7 @@ __metadata: "@openzeppelin/contracts-upgradeable": "npm:^4.9.3" "@trivago/prettier-plugin-sort-imports": "npm:^4.2.1" "@typechain/ethers-v5": "npm:^11.1.2" + "@typechain/ethers-v6": "npm:^0.5.1" "@typechain/hardhat": "npm:^9.1.0" "@typescript-eslint/eslint-plugin": "npm:^7.4.0" "@typescript-eslint/parser": "npm:^7.4.0" @@ -5825,6 +5866,7 @@ __metadata: "@ethersproject/experimental": "npm:^5.7.0" "@ethersproject/hardware-wallets": "npm:^5.7.0" "@ethersproject/providers": "npm:^5.7.2" + "@google-cloud/secret-manager": "npm:^5.5.0" "@hyperlane-xyz/helloworld": "npm:4.0.0-alpha.2" "@hyperlane-xyz/registry": "npm:1.3.0" "@hyperlane-xyz/sdk": "npm:4.0.0-alpha.2" @@ -6502,6 +6544,13 @@ __metadata: languageName: node linkType: hard +"@js-sdsl/ordered-map@npm:^4.4.2": + version: 4.4.2 + resolution: "@js-sdsl/ordered-map@npm:4.4.2" + checksum: ac64e3f0615ecc015461c9f527f124d2edaa9e68de153c1e270c627e01e83d046522d7e872692fd57a8c514578b539afceff75831c0d8b2a9a7a347fbed35af4 + languageName: node + linkType: hard + "@layerzerolabs/lz-evm-messagelib-v2@npm:^2.0.2": version: 2.0.6 resolution: "@layerzerolabs/lz-evm-messagelib-v2@npm:2.0.6" @@ -9339,6 +9388,20 @@ __metadata: languageName: node linkType: hard +"@typechain/ethers-v6@npm:^0.5.1": + version: 0.5.1 + resolution: "@typechain/ethers-v6@npm:0.5.1" + dependencies: + lodash: "npm:^4.17.15" + ts-essentials: "npm:^7.0.1" + peerDependencies: + ethers: 6.x + typechain: ^8.3.2 + typescript: ">=4.7.0" + checksum: 51dd8be3548fe3c061d2a5372beb9214e767e2b69f10c12424b699bba7ff409a13c4bdff2e513ef49046b51153db56489752205541be8fb1775f3b9ad884b85b + languageName: node + linkType: hard + "@typechain/hardhat@npm:^9.1.0": version: 9.1.0 resolution: "@typechain/hardhat@npm:9.1.0" @@ -9440,6 +9503,13 @@ __metadata: languageName: node linkType: hard +"@types/caseless@npm:*": + version: 0.12.5 + resolution: "@types/caseless@npm:0.12.5" + checksum: f6a3628add76d27005495914c9c3873a93536957edaa5b69c63b46fe10b4649a6fecf16b676c1695f46aab851da47ec6047dcf3570fa8d9b6883492ff6d074e0 + languageName: node + linkType: hard + "@types/chai@npm:*, @types/chai@npm:^4.2.21": version: 4.3.1 resolution: "@types/chai@npm:4.3.1" @@ -9608,7 +9678,7 @@ __metadata: languageName: node linkType: hard -"@types/long@npm:^4.0.1": +"@types/long@npm:^4.0.0, @types/long@npm:^4.0.1": version: 4.0.2 resolution: "@types/long@npm:4.0.2" checksum: 68afa05fb20949d88345876148a76f6ccff5433310e720db51ac5ca21cb8cc6714286dbe04713840ddbd25a8b56b7a23aa87d08472fabf06463a6f2ed4967707 @@ -9815,6 +9885,18 @@ __metadata: languageName: node linkType: hard +"@types/request@npm:^2.48.8": + version: 2.48.12 + resolution: "@types/request@npm:2.48.12" + dependencies: + "@types/caseless": "npm:*" + "@types/node": "npm:*" + "@types/tough-cookie": "npm:*" + form-data: "npm:^2.5.0" + checksum: a7b3f9f14cacc18fe235bb8e57eff1232a04bd3fa3dad29371f24a5d96db2cd295a0c8b6b34ed7efa3efbbcff845febb02c9635cd68c54811c947ea66ae22090 + languageName: node + linkType: hard + "@types/resolve@npm:^0.0.8": version: 0.0.8 resolution: "@types/resolve@npm:0.0.8" @@ -9905,6 +9987,13 @@ __metadata: languageName: node linkType: hard +"@types/tough-cookie@npm:*": + version: 4.0.5 + resolution: "@types/tough-cookie@npm:4.0.5" + checksum: 01fd82efc8202670865928629697b62fe9bf0c0dcbc5b1c115831caeb073a2c0abb871ff393d7df1ae94ea41e256cb87d2a5a91fd03cdb1b0b4384e08d4ee482 + languageName: node + linkType: hard + "@types/trusted-types@npm:^2.0.2": version: 2.0.7 resolution: "@types/trusted-types@npm:2.0.7" @@ -10903,6 +10992,15 @@ __metadata: languageName: node linkType: hard +"agent-base@npm:^7.0.2": + version: 7.1.1 + resolution: "agent-base@npm:7.1.1" + dependencies: + debug: "npm:^4.3.4" + checksum: c478fec8f79953f118704d007a38f2a185458853f5c45579b9669372bd0e12602e88dc2ad0233077831504f7cd6fcc8251c383375bba5eaaf563b102938bda26 + languageName: node + linkType: hard + "agentkeepalive@npm:^4.2.1": version: 4.2.1 resolution: "agentkeepalive@npm:4.2.1" @@ -11957,6 +12055,13 @@ __metadata: languageName: node linkType: hard +"buffer-equal-constant-time@npm:1.0.1": + version: 1.0.1 + resolution: "buffer-equal-constant-time@npm:1.0.1" + checksum: 80bb945f5d782a56f374b292770901065bad21420e34936ecbe949e57724b4a13874f735850dd1cc61f078773c4fb5493a41391e7bda40d1fa388d6bd80daaab + languageName: node + linkType: hard + "buffer-from@npm:^1.0.0": version: 1.1.2 resolution: "buffer-from@npm:1.1.2" @@ -13652,6 +13757,18 @@ __metadata: languageName: node linkType: hard +"duplexify@npm:^4.0.0": + version: 4.1.3 + resolution: "duplexify@npm:4.1.3" + dependencies: + end-of-stream: "npm:^1.4.1" + inherits: "npm:^2.0.3" + readable-stream: "npm:^3.1.1" + stream-shift: "npm:^1.0.2" + checksum: b44b98ba0ffac3a658b4b1bf877219e996db288c5ae6f3dc55ca9b2cbef7df60c10eabfdd947f3d73a623eb9975a74a66d6d61e6f26bff90155315adb362aa77 + languageName: node + linkType: hard + "duplexify@npm:^4.1.2": version: 4.1.2 resolution: "duplexify@npm:4.1.2" @@ -13681,6 +13798,15 @@ __metadata: languageName: node linkType: hard +"ecdsa-sig-formatter@npm:1.0.11, ecdsa-sig-formatter@npm:^1.0.11": + version: 1.0.11 + resolution: "ecdsa-sig-formatter@npm:1.0.11" + dependencies: + safe-buffer: "npm:^5.0.1" + checksum: 878e1aab8a42773320bc04c6de420bee21aebd71810e40b1799880a8a1c4594bcd6adc3d4213a0fb8147d4c3f529d8f9a618d7f59ad5a9a41b142058aceda23f + languageName: node + linkType: hard + "ee-first@npm:1.1.1": version: 1.1.1 resolution: "ee-first@npm:1.1.1" @@ -14280,15 +14406,16 @@ __metadata: linkType: hard "eslint@npm:^9.0.0": - version: 9.0.0 - resolution: "eslint@npm:9.0.0" + version: 9.4.0 + resolution: "eslint@npm:9.4.0" dependencies: "@eslint-community/eslint-utils": "npm:^4.2.0" "@eslint-community/regexpp": "npm:^4.6.1" - "@eslint/eslintrc": "npm:^3.0.2" - "@eslint/js": "npm:9.0.0" - "@humanwhocodes/config-array": "npm:^0.12.3" + "@eslint/config-array": "npm:^0.15.1" + "@eslint/eslintrc": "npm:^3.1.0" + "@eslint/js": "npm:9.4.0" "@humanwhocodes/module-importer": "npm:^1.0.1" + "@humanwhocodes/retry": "npm:^0.3.0" "@nodelib/fs.walk": "npm:^1.2.8" ajv: "npm:^6.12.4" chalk: "npm:^4.0.0" @@ -14304,7 +14431,6 @@ __metadata: file-entry-cache: "npm:^8.0.0" find-up: "npm:^5.0.0" glob-parent: "npm:^6.0.2" - graphemer: "npm:^1.4.0" ignore: "npm:^5.2.0" imurmurhash: "npm:^0.1.4" is-glob: "npm:^4.0.0" @@ -14319,7 +14445,7 @@ __metadata: text-table: "npm:^0.2.0" bin: eslint: bin/eslint.js - checksum: 5cf03e14eb114f95bc4e553c8ae2da65ec09d519779beb08e326d98518bce647ce9c8bf3467bcea4cab35a2657cc3a8e945717e784afa4b1bdb9d1ecd9173ba0 + checksum: e2eaae18eb79d543a1ca5420495ea9bf1278f9e25bfa6309ec4e4dae981cba4d731a9b857f5e2f8b5e467adaaf871a635a7eb143a749e7cdcdff4716821628d2 languageName: node linkType: hard @@ -14913,7 +15039,7 @@ __metadata: languageName: node linkType: hard -"extend@npm:~3.0.2": +"extend@npm:^3.0.2, extend@npm:~3.0.2": version: 3.0.2 resolution: "extend@npm:3.0.2" checksum: 59e89e2dc798ec0f54b36d82f32a27d5f6472c53974f61ca098db5d4648430b725387b53449a34df38fd0392045434426b012f302b3cc049a6500ccf82877e4e @@ -15304,7 +15430,7 @@ __metadata: languageName: node linkType: hard -"form-data@npm:^2.2.0": +"form-data@npm:^2.2.0, form-data@npm:^2.5.0": version: 2.5.1 resolution: "form-data@npm:2.5.1" dependencies: @@ -15667,6 +15793,29 @@ __metadata: languageName: node linkType: hard +"gaxios@npm:^6.0.0, gaxios@npm:^6.1.1": + version: 6.6.0 + resolution: "gaxios@npm:6.6.0" + dependencies: + extend: "npm:^3.0.2" + https-proxy-agent: "npm:^7.0.1" + is-stream: "npm:^2.0.0" + node-fetch: "npm:^2.6.9" + uuid: "npm:^9.0.1" + checksum: 9f035590374fd168e7bb3ddda369fc8bd487f16a2308fde18284ccc0f685d0af4ac5e3e38d680a8c6342a9000fbf9d77ce691ee110dbed2feebb659e729c640a + languageName: node + linkType: hard + +"gcp-metadata@npm:^6.1.0": + version: 6.1.0 + resolution: "gcp-metadata@npm:6.1.0" + dependencies: + gaxios: "npm:^6.0.0" + json-bigint: "npm:^1.0.0" + checksum: a0d12a9cb7499fdb9de0fff5406aa220310c1326b80056be8d9b747aae26414f99d14bd795c0ec52ef7d0473eef9d61bb657b8cd3d8186c8a84c4ddbff025fe9 + languageName: node + linkType: hard + "gensync@npm:^1.0.0-beta.2": version: 1.0.0-beta.2 resolution: "gensync@npm:1.0.0-beta.2" @@ -16035,6 +16184,40 @@ __metadata: languageName: node linkType: hard +"google-auth-library@npm:^9.3.0": + version: 9.10.0 + resolution: "google-auth-library@npm:9.10.0" + dependencies: + base64-js: "npm:^1.3.0" + ecdsa-sig-formatter: "npm:^1.0.11" + gaxios: "npm:^6.1.1" + gcp-metadata: "npm:^6.1.0" + gtoken: "npm:^7.0.0" + jws: "npm:^4.0.0" + checksum: 10d5863493f9426107b0f6c4df244b1413a2cacff9589076f906924336d894fe8bc4153d4a3756cebec8a58784ff1a3900c621924f75f004908611fa46d3caa6 + languageName: node + linkType: hard + +"google-gax@npm:^4.0.3": + version: 4.3.3 + resolution: "google-gax@npm:4.3.3" + dependencies: + "@grpc/grpc-js": "npm:~1.10.3" + "@grpc/proto-loader": "npm:^0.7.0" + "@types/long": "npm:^4.0.0" + abort-controller: "npm:^3.0.0" + duplexify: "npm:^4.0.0" + google-auth-library: "npm:^9.3.0" + node-fetch: "npm:^2.6.1" + object-hash: "npm:^3.0.0" + proto3-json-serializer: "npm:^2.0.0" + protobufjs: "npm:7.2.6" + retry-request: "npm:^7.0.0" + uuid: "npm:^9.0.1" + checksum: 63335724e741737b90689e43f8ea5804d82b8f4eaa013ba07166bf6119ef7474d06682d580d72f6b708d6c55251204b1f05db615c3cd84abf2f8f295c50882ec + languageName: node + linkType: hard + "gopd@npm:^1.0.1": version: 1.0.1 resolution: "gopd@npm:1.0.1" @@ -16167,6 +16350,16 @@ __metadata: languageName: node linkType: hard +"gtoken@npm:^7.0.0": + version: 7.1.0 + resolution: "gtoken@npm:7.1.0" + dependencies: + gaxios: "npm:^6.0.0" + jws: "npm:^4.0.0" + checksum: 640392261e55c9242137a81a4af8feb053b57061762cedddcbb6a0d62c2314316161808ac2529eea67d06d69fdc56d82361af50f2d840a04a87ea29e124d7382 + languageName: node + linkType: hard + "h3@npm:^1.10.1, h3@npm:^1.8.2": version: 1.10.2 resolution: "h3@npm:1.10.2" @@ -16726,6 +16919,16 @@ __metadata: languageName: node linkType: hard +"https-proxy-agent@npm:^7.0.1": + version: 7.0.4 + resolution: "https-proxy-agent@npm:7.0.4" + dependencies: + agent-base: "npm:^7.0.2" + debug: "npm:4" + checksum: 405fe582bba461bfe5c7e2f8d752b384036854488b828ae6df6a587c654299cbb2c50df38c4b6ab303502c3c5e029a793fbaac965d1e86ee0be03faceb554d63 + languageName: node + linkType: hard + "human-id@npm:^1.0.2": version: 1.0.2 resolution: "human-id@npm:1.0.2" @@ -18337,6 +18540,27 @@ __metadata: languageName: node linkType: hard +"jwa@npm:^2.0.0": + version: 2.0.0 + resolution: "jwa@npm:2.0.0" + dependencies: + buffer-equal-constant-time: "npm:1.0.1" + ecdsa-sig-formatter: "npm:1.0.11" + safe-buffer: "npm:^5.0.1" + checksum: ab983f6685d99d13ddfbffef9b1c66309a536362a8412d49ba6e687d834a1240ce39290f30ac7dbe241e0ab6c76fee7ff795776ce534e11d148158c9b7193498 + languageName: node + linkType: hard + +"jws@npm:^4.0.0": + version: 4.0.0 + resolution: "jws@npm:4.0.0" + dependencies: + jwa: "npm:^2.0.0" + safe-buffer: "npm:^5.0.1" + checksum: 1d15f4cdea376c6bd6a81002bd2cb0bf3d51d83da8f0727947b5ba3e10cf366721b8c0d099bf8c1eb99eb036e2c55e5fd5efd378ccff75a2b4e0bd10002348b9 + languageName: node + linkType: hard + "keccak@npm:3.0.1": version: 3.0.1 resolution: "keccak@npm:3.0.1" @@ -18919,6 +19143,13 @@ __metadata: languageName: node linkType: hard +"long@npm:^5.0.0": + version: 5.2.3 + resolution: "long@npm:5.2.3" + checksum: 9167ec6947a825b827c30da169a7384eec6c0c9ec2f0b9c74da2e93d81159bbe39fb09c3f13dae9721d4b807ccfa09797a7dd1012f5d478e3e33ca3c78b608e6 + languageName: node + linkType: hard + "loose-envify@npm:^1.0.0, loose-envify@npm:^1.1.0": version: 1.4.0 resolution: "loose-envify@npm:1.4.0" @@ -20114,7 +20345,7 @@ __metadata: languageName: node linkType: hard -"node-fetch@npm:^2.6.0, node-fetch@npm:^2.6.1, node-fetch@npm:^2.6.12": +"node-fetch@npm:^2.6.0, node-fetch@npm:^2.6.1, node-fetch@npm:^2.6.12, node-fetch@npm:^2.6.9": version: 2.7.0 resolution: "node-fetch@npm:2.7.0" dependencies: @@ -20423,6 +20654,13 @@ __metadata: languageName: node linkType: hard +"object-hash@npm:^3.0.0": + version: 3.0.0 + resolution: "object-hash@npm:3.0.0" + checksum: f498d456a20512ba7be500cef4cf7b3c183cc72c65372a549c9a0e6dd78ce26f375e9b1315c07592d3fde8f10d5019986eba35970570d477ed9a2a702514432a + languageName: node + linkType: hard + "object-inspect@npm:^1.12.0, object-inspect@npm:^1.12.2, object-inspect@npm:^1.9.0": version: 1.12.2 resolution: "object-inspect@npm:1.12.2" @@ -21345,6 +21583,35 @@ __metadata: languageName: node linkType: hard +"proto3-json-serializer@npm:^2.0.0": + version: 2.0.1 + resolution: "proto3-json-serializer@npm:2.0.1" + dependencies: + protobufjs: "npm:^7.2.5" + checksum: dc4319c90e2412b9647f13dd1df2a6338ee3a07e2fd693c5ce4d1728c3730d913ebdb6d656f400ae4214a70bf0791ca0bc04d53b2cbdd75394bf0b175898443b + languageName: node + linkType: hard + +"protobufjs@npm:7.2.6": + version: 7.2.6 + resolution: "protobufjs@npm:7.2.6" + dependencies: + "@protobufjs/aspromise": "npm:^1.1.2" + "@protobufjs/base64": "npm:^1.1.2" + "@protobufjs/codegen": "npm:^2.0.4" + "@protobufjs/eventemitter": "npm:^1.1.0" + "@protobufjs/fetch": "npm:^1.1.0" + "@protobufjs/float": "npm:^1.0.2" + "@protobufjs/inquire": "npm:^1.1.0" + "@protobufjs/path": "npm:^1.1.2" + "@protobufjs/pool": "npm:^1.1.0" + "@protobufjs/utf8": "npm:^1.1.0" + "@types/node": "npm:>=13.7.0" + long: "npm:^5.0.0" + checksum: 81ab853d28c71998d056d6b34f83c4bc5be40cb0b416585f99ed618aed833d64b2cf89359bad7474d345302f2b5e236c4519165f8483d7ece7fd5b0d9ac13f8b + languageName: node + linkType: hard + "protobufjs@npm:^6.8.8, protobufjs@npm:~6.11.2, protobufjs@npm:~6.11.3": version: 6.11.4 resolution: "protobufjs@npm:6.11.4" @@ -21369,6 +21636,26 @@ __metadata: languageName: node linkType: hard +"protobufjs@npm:^7.2.5": + version: 7.3.0 + resolution: "protobufjs@npm:7.3.0" + dependencies: + "@protobufjs/aspromise": "npm:^1.1.2" + "@protobufjs/base64": "npm:^1.1.2" + "@protobufjs/codegen": "npm:^2.0.4" + "@protobufjs/eventemitter": "npm:^1.1.0" + "@protobufjs/fetch": "npm:^1.1.0" + "@protobufjs/float": "npm:^1.0.2" + "@protobufjs/inquire": "npm:^1.1.0" + "@protobufjs/path": "npm:^1.1.2" + "@protobufjs/pool": "npm:^1.1.0" + "@protobufjs/utf8": "npm:^1.1.0" + "@types/node": "npm:>=13.7.0" + long: "npm:^5.0.0" + checksum: aff4aa2a3a2f011accb51e23fcae122acbee35cb761abe51f799675a61ab39ad9a506911f307e0fdb9a1703bed1f522cfbdaafaeefd2b3aaca2ddc18f03029d9 + languageName: node + linkType: hard + "proxy-addr@npm:~2.0.7": version: 2.0.7 resolution: "proxy-addr@npm:2.0.7" @@ -22254,6 +22541,17 @@ __metadata: languageName: node linkType: hard +"retry-request@npm:^7.0.0": + version: 7.0.2 + resolution: "retry-request@npm:7.0.2" + dependencies: + "@types/request": "npm:^2.48.8" + extend: "npm:^3.0.2" + teeny-request: "npm:^9.0.0" + checksum: 8f4c927d41dd575fc460aad7b762fb0a33542097201c3c1a31529ad17fa8af3ac0d2a45bf4a2024d079913e9c2dd431566070fe33321c667ac87ebb400de5917 + languageName: node + linkType: hard + "retry@npm:0.13.1": version: 0.13.1 resolution: "retry@npm:0.13.1" @@ -23391,7 +23689,16 @@ __metadata: languageName: node linkType: hard -"stream-shift@npm:^1.0.0": +"stream-events@npm:^1.0.5": + version: 1.0.5 + resolution: "stream-events@npm:1.0.5" + dependencies: + stubs: "npm:^3.0.0" + checksum: 969ce82e34bfbef5734629cc06f9d7f3705a9ceb8fcd6a526332f9159f1f8bbfdb1a453f3ced0b728083454f7706adbbe8428bceb788a0287ca48ba2642dc3fc + languageName: node + linkType: hard + +"stream-shift@npm:^1.0.0, stream-shift@npm:^1.0.2": version: 1.0.3 resolution: "stream-shift@npm:1.0.3" checksum: a24c0a3f66a8f9024bd1d579a533a53be283b4475d4e6b4b3211b964031447bdf6532dd1f3c2b0ad66752554391b7c62bd7ca4559193381f766534e723d50242 @@ -23691,6 +23998,13 @@ __metadata: languageName: node linkType: hard +"stubs@npm:^3.0.0": + version: 3.0.0 + resolution: "stubs@npm:3.0.0" + checksum: dec7b82186e3743317616235c59bfb53284acc312cb9f4c3e97e2205c67a5c158b0ca89db5927e52351582e90a2672822eeaec9db396e23e56893d2a8676e024 + languageName: node + linkType: hard + "styled-jsx@npm:5.1.1": version: 5.1.1 resolution: "styled-jsx@npm:5.1.1" @@ -23944,6 +24258,19 @@ __metadata: languageName: node linkType: hard +"teeny-request@npm:^9.0.0": + version: 9.0.0 + resolution: "teeny-request@npm:9.0.0" + dependencies: + http-proxy-agent: "npm:^5.0.0" + https-proxy-agent: "npm:^5.0.0" + node-fetch: "npm:^2.6.9" + stream-events: "npm:^1.0.5" + uuid: "npm:^9.0.0" + checksum: 44daabb6c2e239c3daed0218ebdafb50c7141c16d7257a6cfef786dbff56d7853c2c02c97934f7ed57818ce5861ac16c5f52f3a16fa292bd4caf53483d386443 + languageName: node + linkType: hard + "term-size@npm:^2.1.0": version: 2.2.1 resolution: "term-size@npm:2.2.1" From 897f627f093b2fd91d115bdf36c615d2a28bc957 Mon Sep 17 00:00:00 2001 From: Connor McEwen Date: Mon, 17 Jun 2024 14:50:07 -0400 Subject: [PATCH 41/73] fix: remove extra rust code from merge --- .../contract_sync/cursors/sequence_aware/backward.rs | 11 ----------- .../contract_sync/cursors/sequence_aware/forward.rs | 12 ------------ 2 files changed, 23 deletions(-) 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 index 2ef9e3f27..6a0f66a78 100644 --- a/rust/hyperlane-base/src/contract_sync/cursors/sequence_aware/backward.rs +++ b/rust/hyperlane-base/src/contract_sync/cursors/sequence_aware/backward.rs @@ -327,17 +327,6 @@ impl BackwardSequenceAwareSyncCursor { } } -impl Debug for BackwardSequenceAwareSyncCursor { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("BackwardSequenceAwareSyncCursor") - .field("chunk_size", &self.chunk_size) - .field("current_indexing_snapshot", &self.current_indexing_snapshot) - .field("last_indexed_snapshot", &self.last_indexed_snapshot) - .field("index_mode", &self.index_mode) - .finish() - } -} - #[async_trait] impl ContractSyncCursor for BackwardSequenceAwareSyncCursor 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 index 5d22374b2..7314e2a00 100644 --- a/rust/hyperlane-base/src/contract_sync/cursors/sequence_aware/forward.rs +++ b/rust/hyperlane-base/src/contract_sync/cursors/sequence_aware/forward.rs @@ -403,18 +403,6 @@ impl ForwardSequenceAwareSyncCursor { } } -impl Debug for ForwardSequenceAwareSyncCursor { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("ForwardSequenceAwareSyncCursor") - .field("chunk_size", &self.chunk_size) - .field("current_indexing_snapshot", &self.current_indexing_snapshot) - .field("last_indexed_snapshot", &self.last_indexed_snapshot) - .field("target_snapshot", &self.target_snapshot) - .field("index_mode", &self.index_mode) - .finish() - } -} - #[async_trait] impl ContractSyncCursor for ForwardSequenceAwareSyncCursor From b0828b3d0eb95921a8753bb0c08b36b37ca0af15 Mon Sep 17 00:00:00 2001 From: Paul Balaji Date: Tue, 18 Jun 2024 19:17:29 +0100 Subject: [PATCH 42/73] feat: reintroduce `ism read` and `hook read` commands (#3990) the original functionality of these commands got lost across a couple of refactors 1. `ism read`/`hook read` was merged to become `core read`, which still had the purpose of "gimme config at this address" 2. then with a recent change, `core read` became "gimme the ism/hook configs relating to this mailbox address" This PR adds the original functionality back of returning the ISM/Hook configs for *any* ISM/Hook address (on EVM) drive-by: - update mainnet config - fix typescript linting errors/warnings --- .changeset/new-taxis-fry.md | 5 ++ .github/workflows/test.yml | 2 +- rust/config/mainnet_config.json | 81 +++++++++++++------ typescript/cli/src/avs/stakeRegistry.ts | 2 +- typescript/cli/src/commands/core.ts | 2 +- typescript/cli/src/commands/hook.ts | 49 +++++++++++ typescript/cli/src/commands/ism.ts | 54 +++++++++++++ typescript/cli/src/validator/address.ts | 20 ++--- typescript/sdk/src/ism/metadata/multisig.ts | 21 ++++- .../providers/SmartProvider/SmartProvider.ts | 2 +- .../sdk/src/token/adapters/EvmTokenAdapter.ts | 8 +- typescript/sdk/src/token/config.ts | 2 +- 12 files changed, 203 insertions(+), 45 deletions(-) create mode 100644 .changeset/new-taxis-fry.md create mode 100644 typescript/cli/src/commands/hook.ts create mode 100644 typescript/cli/src/commands/ism.ts diff --git a/.changeset/new-taxis-fry.md b/.changeset/new-taxis-fry.md new file mode 100644 index 000000000..88882518f --- /dev/null +++ b/.changeset/new-taxis-fry.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/cli': minor +--- + +Reintroduce `ism read` and `hook read` commands diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b83bc9c35..6119eb04a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -329,7 +329,7 @@ jobs: cli-e2e: runs-on: larger-runner - if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.base_ref == 'main') || github.event_name == 'merge_group' + if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.base_ref == 'main' || github.base_ref == 'cli-2.0') || github.event_name == 'merge_group' needs: [yarn-build, checkout-registry] strategy: matrix: diff --git a/rust/config/mainnet_config.json b/rust/config/mainnet_config.json index 8b56ae474..e87c823c9 100644 --- a/rust/config/mainnet_config.json +++ b/rust/config/mainnet_config.json @@ -19,7 +19,7 @@ "chainId": 888888888, "displayName": "Ancient8", "domainId": 888888888, - "domainRoutingIsm": "0xB6F0f1267B01C27326F61a4B4fe2c73751802685", + "domainRoutingIsm": "0x477145b11E1a71fEb658d96A0E27F19495121504", "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0x5E01d8F34b629E3f92d69546bbc4142A7Adee7e9", "gasCurrencyCoinGeckoId": "ethereum", @@ -27,7 +27,7 @@ "from": 2507127 }, "interchainGasPaymaster": "0x8F1E22d309baa69D398a03cc88E9b46037e988AA", - "interchainSecurityModule": "0xBd3C7253F08c040eDB9c54e7CD4f8a5fd1eb935D", + "interchainSecurityModule": "0xa10686c87d47C8b78c846E77BE2f96607C7c44F4", "isTestnet": false, "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0x811808Dd29ba8B0FC6C0ec0b5537035E59745162", @@ -38,7 +38,7 @@ "symbol": "ETH" }, "pausableHook": "0x66DC49405Ae2956f7E87FEAa9fE8f506C8987462", - "pausableIsm": "0xcf678903c003651DB0bb933820259A16ea9d95e4", + "pausableIsm": "0xF8DbA46fF9D8ef650052c89CA2Df793FaBc375F9", "protocol": "ethereum", "protocolFee": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", "proxyAdmin": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", @@ -48,7 +48,7 @@ } ], "staticAggregationHookFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", - "staticAggregationIsm": "0xBd3C7253F08c040eDB9c54e7CD4f8a5fd1eb935D", + "staticAggregationIsm": "0xc6ec1364d1ce3E963Fa65A0bDF57eC722478e1FB", "staticAggregationIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", "staticMerkleRootMultisigIsmFactory": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", "staticMessageIdMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", @@ -86,7 +86,7 @@ "interchainAccountIsm": "0xfa8bfcE55B3A0631dF38257615cEF7FCD3523A48", "interchainAccountRouter": "0xCD0CFFf6eFD943b4b81f2c15847730dbcD30e3aE", "interchainGasPaymaster": "0x3b6044acd6767f017e99318AA6Ef93b7B06A5a22", - "interchainSecurityModule": "0x96845a0469363f90779f6D5cd49D79bDDAc69429", + "interchainSecurityModule": "0xd12C017529BE32c23150313F1E473B76e6B19773", "mailbox": "0x979Ca5202784112f4738403dBec5D0F3B9daabB9", "merkleTreeHook": "0x748040afB89B8FdBb992799808215419d36A0930", "name": "arbitrum", @@ -152,7 +152,7 @@ "interchainAccountIsm": "0x786c26C1857032617c215f265509d6E44e44Bfe3", "interchainAccountRouter": "0xA967A6CE0e73fAf672843DECaA372511996E8852", "interchainGasPaymaster": "0x95519ba800BBd0d34eeAE026fEc620AD978176C0", - "interchainSecurityModule": "0xe7a61510EA7197281b49e5bdf1798608d5132595", + "interchainSecurityModule": "0xB7D96FcD923267640BcffC7c3F23530E6C7A4209", "mailbox": "0xFf06aFcaABaDDd1fb08371f9ccA15D73D51FeBD6", "merkleTreeHook": "0x84eea61D679F42D92145fA052C89900CBAccE95A", "name": "avalanche", @@ -218,7 +218,7 @@ "interchainAccountIsm": "0x861908E6c8F992537F557da5Fb5876836036b347", "interchainAccountRouter": "0xa85F9e4fdA2FFF1c07f2726a630443af3faDF830", "interchainGasPaymaster": "0xc3F23848Ed2e04C0c6d41bd7804fa8f89F940B94", - "interchainSecurityModule": "0x77bE0b5aE400675063Ce2B2B0d692D9341f4b193", + "interchainSecurityModule": "0xe7aaFbA826B0bDC60aD09b7b8cA8175c9A89cE0b", "mailbox": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", "merkleTreeHook": "0x19dc38aeae620380430C200a6E990D5Af5480117", "name": "base", @@ -275,11 +275,12 @@ "domainRoutingIsmFactory": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "fallbackRoutingHook": "0x6Fae4D9935E2fcb11fC79a64e917fb2BF14DaFaa", "gasCurrencyCoinGeckoId": "ethereum", + "gnosisSafeTransactionServiceUrl": "https://transaction.blast-safe.io", "index": { "from": 2496427 }, "interchainGasPaymaster": "0xB3fCcD379ad66CED0c91028520C64226611A48c9", - "interchainSecurityModule": "0x208263bB303B2a737642fB13C765F106a2591be8", + "interchainSecurityModule": "0xdE516712D58166257E03254BD596CA726417a837", "mailbox": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7", "merkleTreeHook": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", "name": "blast", @@ -338,7 +339,7 @@ "interchainAccountIsm": "0xB274Bbbc1df5f1d1763216A93d473fde6f5de043", "interchainAccountRouter": "0x4BBd67dC995572b40Dc6B3eB6CdE5185a5373868", "interchainGasPaymaster": "0x78E25e7f84416e69b9339B0A6336EB6EFfF6b451", - "interchainSecurityModule": "0xfA360ff588623A026BF19A1801F2A8F1f045fa33", + "interchainSecurityModule": "0x4C23e778aF68a54cb59A44581b90E78b35763C83", "mailbox": "0x2971b9Aec44bE4eb673DF1B88cDB57b96eefe8a4", "merkleTreeHook": "0xFDb9Cd5f9daAA2E4474019405A328a88E7484f26", "name": "bsc", @@ -411,7 +412,7 @@ "interchainAccountIsm": "0x30a8DEc5318e2aAa9ad5b069fC606c4CfF6f5676", "interchainAccountRouter": "0x4ED23E3885e1651E62564F78817D91865beba575", "interchainGasPaymaster": "0x571f1435613381208477ac5d6974310d88AC7cB7", - "interchainSecurityModule": "0x0dcb01D4ABfa73fadB17C4B0e8cd52A38BD52c66", + "interchainSecurityModule": "0x1b90cCF49e45a87F751b344864e4246D1a57a100", "mailbox": "0x50da3B3907A08a24fe4999F4Dcf337E8dC7954bb", "merkleTreeHook": "0x04dB778f05854f26E67e0a66b740BBbE9070D366", "name": "celo", @@ -476,7 +477,7 @@ "interchainAccountIsm": "0x609707355a53d2aAb6366f48E2b607C599D26B29", "interchainAccountRouter": "0x8dBae9B1616c46A20591fE0006Bf015E28ca5cC9", "interchainGasPaymaster": "0x9e6B1022bE9BBF5aFd152483DAD9b88911bC8611", - "interchainSecurityModule": "0x8CE0c6cAf18DbF5882b35F26E28412f3E9AbDeca", + "interchainSecurityModule": "0x355F2c5532521ca6Df8A908641Ed69eD0619466d", "mailbox": "0xc005dc82818d67AF737725bD4bf75435d065D239", "merkleTreeHook": "0x48e6c30B97748d1e2e03bf3e9FbE3890ca5f8CCA", "name": "ethereum", @@ -542,7 +543,7 @@ "interchainAccountIsm": "0x5a56dff3D92D635372718f86e6dF09C1129CFf53", "interchainAccountRouter": "0x5E59EBAedeB691408EBAcF6C37218fa2cFcaC9f2", "interchainGasPaymaster": "0xDd260B99d302f0A3fF885728c086f729c06f227f", - "interchainSecurityModule": "0x5DB7edF8C1CF91e34895dB2e4b28d8b9C68ddC7B", + "interchainSecurityModule": "0x613D9ab2dd6cCFa2763d86FdEE3d96C4Ef4C2E96", "mailbox": "0xaD09d78f4c6b9dA2Ae82b1D34107802d380Bb74f", "merkleTreeHook": "0x2684C6F89E901987E1FdB7649dC5Be0c57C61645", "name": "gnosis", @@ -592,11 +593,13 @@ "reorgPeriod": 0 }, "chainId": 2525, + "customHook": "0xA376b27212D608324808923Add679A2c9FAFe9Da", "displayName": "Injective EVM", "displayNameShort": "inEVM", "domainId": 2525, "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", + "fallbackRoutingHook": "0xA376b27212D608324808923Add679A2c9FAFe9Da", "gasCurrencyCoinGeckoId": "injective-protocol", "index": { "from": 18972465 @@ -604,7 +607,7 @@ "interchainAccountIsm": "0x31894E7a734540B343d67E491148EB4FC9f7A45B", "interchainAccountRouter": "0x4E55aDA3ef1942049EA43E904EB01F4A0a9c39bd", "interchainGasPaymaster": "0x19dc38aeae620380430C200a6E990D5Af5480117", - "interchainSecurityModule": "0x440f7AD246F3e75df88a6338E8A33e91DA4B2B05", + "interchainSecurityModule": "0xC1a7a7b15d2B243f7c6Dbbdd6e8A2C2434cdf7BF", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0x0972954923a1e2b2aAb04Fa0c4a0797e5989Cd65", "name": "inevm", @@ -629,6 +632,7 @@ "staticMerkleRootMultisigIsmFactory": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", "staticMessageIdMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", "storageGasOracle": "0x6119E37Bd66406A1Db74920aC79C15fB8411Ba76", + "testRecipient": "0x28291a7062afA569104bEd52F7AcCA3dD2FafD11", "timelockController": "0x0000000000000000000000000000000000000000", "validatorAnnounce": "0x15ab173bDB6832f9b64276bA128659b0eD77730B" }, @@ -713,7 +717,7 @@ "interchainAccountIsm": "0xA34ceDf9068C5deE726C67A4e1DCfCc2D6E2A7fD", "interchainAccountRouter": "0x0f6fF770Eda6Ba1433C39cCf47d4059b254224Aa", "interchainGasPaymaster": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", - "interchainSecurityModule": "0xEda7cCD2A8CF717dc997D0002e363e4D10bF5c0d", + "interchainSecurityModule": "0x83319BD846D54E49C850027191D094D5E9114339", "isTestnet": false, "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x149db7afD694722747035d5AEC7007ccb6F8f112", @@ -724,6 +728,7 @@ "symbol": "ETH" }, "pausableHook": "0x7556a0E61d577D921Cba8Fca0d7D6299d36E607E", + "pausableIsm": "0x6119B76720CcfeB3D256EC1b91218EEfFD6756E1", "protocol": "ethereum", "protocolFee": "0xd83A4F747fE80Ed98839e05079B1B7Fe037b1638", "proxyAdmin": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", @@ -733,6 +738,7 @@ } ], "staticAggregationHookFactory": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "staticAggregationIsm": "0x845A3feB4BcdC32a457c4051F67d3950FC6Fd1d1", "staticAggregationIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "staticMerkleRootMultisigIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", "staticMessageIdMultisigIsmFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", @@ -743,6 +749,7 @@ "validatorAnnounce": "0x2fa5F5C96419C222cDbCeC797D696e6cE428A7A9" }, "mode": { + "aggregationHook": "0x80D80cfBa98dD2d456ECd43Dcc1f852D5C4EeD7a", "blockExplorers": [ { "apiUrl": "https://explorer.mode.network/api", @@ -759,14 +766,16 @@ "chainId": 34443, "displayName": "Mode", "domainId": 34443, + "domainRoutingIsm": "0xB6F0f1267B01C27326F61a4B4fe2c73751802685", "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0x8F1E22d309baa69D398a03cc88E9b46037e988AA", "gasCurrencyCoinGeckoId": "ethereum", + "gnosisSafeTransactionServiceUrl": "https://transaction-mode.safe.optimism.io", "index": { "from": 6817759 }, "interchainGasPaymaster": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", - "interchainSecurityModule": "0x8dfE6790DbB2Ecc1bEdb0eECfc1Ff467Ae5d8C89", + "interchainSecurityModule": "0xEe9443bc2Df7C10327077c605120CDd95e8Ca75F", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0xE2ee936bEa8e42671c400aC96dE198E06F2bA2A6", "name": "mode", @@ -776,6 +785,7 @@ "symbol": "ETH" }, "pausableHook": "0xA1ac41d8A663fd317cc3BD94C7de92dC4BA4a882", + "pausableIsm": "0xe243Fb51d91c5DE62afAbE44F7Ed2D4DC51668C6", "protocol": "ethereum", "protocolFee": "0xea820f9BCFD5E16a0dd42071EB61A29874Ad81A4", "proxyAdmin": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", @@ -785,6 +795,7 @@ } ], "staticAggregationHookFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", + "staticAggregationIsm": "0x6BdA2074b7edCE8c4e4cD3D35517267468Aed93F", "staticAggregationIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", "staticMerkleRootMultisigIsmFactory": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", "staticMessageIdMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", @@ -811,6 +822,7 @@ "chainId": 1284, "displayName": "Moonbeam", "domainId": 1284, + "domainRoutingIsm": "0x7Faa23CEdA03364A79e05259e07D5E358E7400F7", "domainRoutingIsmFactory": "0x8061Af3A459093540d17823D651BC5E2A92669a7", "fallbackRoutingHook": "0x6C2D6eA0969F7Aa0A850CCA88c7BFACa563B2361", "gnosisSafeTransactionServiceUrl": "https://transaction.multisig.moonbeam.network", @@ -820,7 +832,7 @@ "interchainAccountIsm": "0x799eA6f430f5CA901b59335fFC2fA10531106009", "interchainAccountRouter": "0x6b142f596FFc761ac3fFaaC1ecaDe54f4EE09977", "interchainGasPaymaster": "0x14760E32C0746094cF14D97124865BC7F0F7368F", - "interchainSecurityModule": "0x373836DFa82f2D27ec79Ca32A197Aa1665F0E1e9", + "interchainSecurityModule": "0x3db9D7c5cD0Aa312AB1262C711C10DeBA8303388", "mailbox": "0x094d03E751f49908080EFf000Dd6FD177fd44CC3", "merkleTreeHook": "0x87403b85f6f316e7ba91ba1fa6C3Fb7dD4095547", "name": "moonbeam", @@ -830,6 +842,7 @@ "symbol": "GLMR" }, "pausableHook": "0xe28f2AEEB42ee83CAd068D9A9a449c8b868C137f", + "pausableIsm": "0x58062b26193B28000Cd991Df767f3A2674502de8", "protocol": "ethereum", "protocolFee": "0xCd3e29A9D293DcC7341295996a118913F7c582c0", "proxyAdmin": "0x6A9cdA3dd1F593983BFd142Eb35e6ce4137bd5ce", @@ -839,6 +852,7 @@ } ], "staticAggregationHookFactory": "0x59cC3E7A49DdC4893eB8754c7908f96072A7DbE8", + "staticAggregationIsm": "0xDAAfa04d38d95f5B8418786AE0F7ee5B962ee92B", "staticAggregationIsmFactory": "0x40c6Abcb6A2CdC8882d4bEcaC47927005c7Bb8c2", "staticMerkleRootMultisigIsmFactory": "0xE2f485bc031Feb5a4C41C1967bf028653d75f0C3", "staticMessageIdMultisigIsmFactory": "0x84Df48F8f241f11d0fA302d09d73030429Bd9C73", @@ -934,6 +948,7 @@ "chainId": 10, "displayName": "Optimism", "domainId": 10, + "domainRoutingIsm": "0xDFfFCA9320E2c7530c61c4946B4c2376A1901dF2", "domainRoutingIsmFactory": "0xD2e905108c5e44dADA680274740f896Ea96Cf2Fb", "fallbackRoutingHook": "0xD4b132C6d4AA93A4247F1A91e1ED929c0572a43d", "gasCurrencyCoinGeckoId": "ethereum", @@ -944,7 +959,7 @@ "interchainAccountIsm": "0x0389faCac114023C123E22F3E54394944cAbcb48", "interchainAccountRouter": "0x33Ef006E7083BB38E0AFe3C3979F4e9b84415bf1", "interchainGasPaymaster": "0xD8A76C4D91fCbB7Cc8eA795DFDF870E48368995C", - "interchainSecurityModule": "0x04938856bE60c8e734ffDe5f720E2238302BE8D2", + "interchainSecurityModule": "0x32Ce76b9FfD0BbA0e43a2C27c2cAc90E82C48B95", "mailbox": "0xd4C1905BB1D26BC93DAC913e13CaCC278CdCC80D", "merkleTreeHook": "0x68eE9bec9B4dbB61f69D9D293Ae26a5AACb2e28f", "name": "optimism", @@ -954,6 +969,7 @@ "symbol": "ETH" }, "pausableHook": "0xf753CA2269c8A7693ce1808b5709Fbf36a65D47A", + "pausableIsm": "0xD84D8114cCfa5c2403E56aBf754da529430704F0", "protocol": "ethereum", "protocolFee": "0xD71Ff941120e8f935b8b1E2C1eD72F5d140FF458", "proxyAdmin": "0xE047cb95FB3b7117989e911c6afb34771183fC35", @@ -963,6 +979,7 @@ } ], "staticAggregationHookFactory": "0x15DEeAB8dECDe553bb0B1F9C00984cbcae1af3D7", + "staticAggregationIsm": "0xdF6316DF574974110DCC94BB4E520B09Fe3CbEf9", "staticAggregationIsmFactory": "0x7491843F3A5Ba24E0f17a22645bDa04A1Ae2c584", "staticMerkleRootMultisigIsmFactory": "0xCA6Cb9Bc3cfF9E11003A06617cF934B684Bc78BC", "staticMessageIdMultisigIsmFactory": "0xAa4Be20E9957fE21602c74d7C3cF5CB1112EA9Ef", @@ -1034,7 +1051,7 @@ "interchainAccountIsm": "0x90384bC552e3C48af51Ef7D9473A9bF87431f5c7", "interchainAccountRouter": "0x5e80f3474825B61183c0F0f0726796F589082420", "interchainGasPaymaster": "0x0071740Bf129b05C4684abfbBeD248D80971cce2", - "interchainSecurityModule": "0xe289bD204Dbb4F3aaFA27Dbe5751C71e101CFD80", + "interchainSecurityModule": "0x1c39BD73C933c6B1b14685ABD28F996A6a0e306f", "mailbox": "0x5d934f4e2f797775e53561bB72aca21ba36B96BB", "merkleTreeHook": "0x73FbD25c3e817DC4B4Cd9d00eff6D83dcde2DfF6", "name": "polygon", @@ -1093,6 +1110,7 @@ "displayName": "Polygon zkEVM", "displayNameShort": "zkEVM", "domainId": 1101, + "domainRoutingIsm": "0x8b6862a784f634F4C8E1cbb04c9DA3dB637B7EaA", "domainRoutingIsmFactory": "0xe4057c5B0c43Dc18E36b08C39B419F190D29Ac2d", "fallbackRoutingHook": "0x01aE937A7B05d187bBCBE80F44F41879D3D335a4", "gasCurrencyCoinGeckoId": "ethereum", @@ -1103,7 +1121,7 @@ "interchainAccountIsm": "0xC49aF4965264FA7BB6424CE37aA06773ad177224", "interchainAccountRouter": "0xF15D70941dE2Bf95A23d6488eBCbedE0a444137f", "interchainGasPaymaster": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", - "interchainSecurityModule": "0xf2BEE9D2c15Ba9D7e06799B5912dE1F05533c141", + "interchainSecurityModule": "0xb7556707f04BEF8888294C18c447bE1e4446bF7D", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x149db7afD694722747035d5AEC7007ccb6F8f112", "name": "polygonzkevm", @@ -1113,6 +1131,7 @@ "symbol": "ETH" }, "pausableHook": "0xc2FbB9411186AB3b1a6AFCCA702D1a80B48b197c", + "pausableIsm": "0x784b9D0f4eF9fb8444DfB5d24AB221C9D1A85395", "protocol": "ethereum", "protocolFee": "0xd83A4F747fE80Ed98839e05079B1B7Fe037b1638", "proxyAdmin": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", @@ -1125,14 +1144,17 @@ } ], "staticAggregationHookFactory": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "staticAggregationIsm": "0xAe7d2FA2aFc57Ce9F05930d403673A267b3efE50", "staticAggregationIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "staticMerkleRootMultisigIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", "staticMessageIdMultisigIsmFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", "storageGasOracle": "0x19dc38aeae620380430C200a6E990D5Af5480117", + "testRecipient": "0xD127D4549cb4A5B2781303a4fE99a10EAd13263A", "timelockController": "0x0000000000000000000000000000000000000000", "validatorAnnounce": "0x2fa5F5C96419C222cDbCeC797D696e6cE428A7A9" }, "redstone": { + "aggregationHook": "0x7bC13D23eD161E152a05c71D037b4642EA61B8eF", "blockExplorers": [ { "apiUrl": "https://explorer.redstone.xyz/api", @@ -1149,6 +1171,7 @@ "chainId": 690, "displayName": "Redstone", "domainId": 690, + "domainRoutingIsm": "0x5D1e7D7c5B9e6dDC8439F67F10c578f2A1084f6F", "domainRoutingIsmFactory": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", "fallbackRoutingHook": "0xA1ac41d8A663fd317cc3BD94C7de92dC4BA4a882", "gasCurrencyCoinGeckoId": "ethereum", @@ -1156,7 +1179,7 @@ "from": 1797579 }, "interchainGasPaymaster": "0x2Fa570E83009eaEef3a1cbd496a9a30F05266634", - "interchainSecurityModule": "0xF4689C7fA4920C91a6EEEd59630C9C8da7a77D40", + "interchainSecurityModule": "0xD10AD7d927817558468Ce0F0ab1edEF8D230E2d8", "mailbox": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", "merkleTreeHook": "0x8F1E22d309baa69D398a03cc88E9b46037e988AA", "name": "redstone", @@ -1166,6 +1189,7 @@ "symbol": "ETH" }, "pausableHook": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", + "pausableIsm": "0xD53Fdbb7537aCa82bAf7cCA7204088f15b52796a", "protocol": "ethereum", "protocolFee": "0x26f32245fCF5Ad53159E875d5Cae62aEcf19c2d4", "proxyAdmin": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", @@ -1175,6 +1199,7 @@ } ], "staticAggregationHookFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", + "staticAggregationIsm": "0x3Bb9244a2aBBb710c933e8D82Ff8F0C200F3c036", "staticAggregationIsmFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", "staticMerkleRootMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", "staticMessageIdMultisigIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", @@ -1200,10 +1225,11 @@ "chainId": 534352, "displayName": "Scroll", "domainId": 534352, + "domainRoutingIsm": "0x4d02AfFc3F030c887e2f914B8B67E0B845e034fD", "domainRoutingIsmFactory": "0xe03dad16074BC5EEA9A9311257BF02Eb0B6AAA2b", "fallbackRoutingHook": "0xDa7cECb05C4aeB02c1aFDE277d4306a2da7Bd762", "gasCurrencyCoinGeckoId": "ethereum", - "gnosisSafeTransactionServiceUrl": "https://transaction.safe.scroll.xyz", + "gnosisSafeTransactionServiceUrl": "https://safe-transaction-scroll.safe.global", "index": { "chunk": 999, "from": 271840 @@ -1211,7 +1237,7 @@ "interchainAccountIsm": "0xb89c6ED617f5F46175E41551350725A09110bbCE", "interchainAccountRouter": "0x9629c28990F11c31735765A6FD59E1E1bC197DbD", "interchainGasPaymaster": "0xBF12ef4B9f307463D3FB59c3604F294dDCe287E2", - "interchainSecurityModule": "0xaDc0cB48E8DB81855A930C0C1165ea3dCe4Ba5C7", + "interchainSecurityModule": "0x0E6aFC8a7b5223cAD7a7c346da1B2e6ECBaA93f0", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0x6119E37Bd66406A1Db74920aC79C15fB8411Ba76", "name": "scroll", @@ -1221,6 +1247,7 @@ "symbol": "ETH" }, "pausableHook": "0x4Eb82Ee35b0a1c1d776E3a3B547f9A9bA6FCC9f2", + "pausableIsm": "0x11Fa12DBaCe771E293e19743feA342e378C6341F", "protocol": "ethereum", "protocolFee": "0xc3F23848Ed2e04C0c6d41bd7804fa8f89F940B94", "proxyAdmin": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", @@ -1230,10 +1257,12 @@ } ], "staticAggregationHookFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", + "staticAggregationIsm": "0xAC0F1820F1F3fEd26293B8714464ca431824f823", "staticAggregationIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", "staticMerkleRootMultisigIsmFactory": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", "staticMessageIdMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", "storageGasOracle": "0x481171eb1aad17eDE6a56005B7F1aB00C581ef13", + "testRecipient": "0x674f4698d063cE4C0d604c88dD7D542De72f327f", "timelockController": "0x0000000000000000000000000000000000000000", "transactionOverrides": { "gasPrice": 2000000000 @@ -1269,7 +1298,7 @@ "interchainAccountIsm": "0xD1E267d2d7876e97E217BfE61c34AB50FEF52807", "interchainAccountRouter": "0x1956848601549de5aa0c887892061fA5aB4f6fC4", "interchainGasPaymaster": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", - "interchainSecurityModule": "0xf8F3AF5F6B8f319364c339c0b8cA5975481901eD", + "interchainSecurityModule": "0xA76F4620ac1e97d273B2C9Ca71805c8afD792098", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0x149db7afD694722747035d5AEC7007ccb6F8f112", "name": "viction", @@ -1303,6 +1332,7 @@ "validatorAnnounce": "0x2fa5F5C96419C222cDbCeC797D696e6cE428A7A9" }, "zetachain": { + "aggregationHook": "0x80D80cfBa98dD2d456ECd43Dcc1f852D5C4EeD7a", "blockExplorers": [ { "apiUrl": "https://explorer.zetachain.com", @@ -1319,6 +1349,7 @@ "chainId": 7000, "displayName": "ZetaChain", "domainId": 7000, + "domainRoutingIsm": "0xaDc0cB48E8DB81855A930C0C1165ea3dCe4Ba5C7", "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0x8F1E22d309baa69D398a03cc88E9b46037e988AA", "gasCurrencyCoinGeckoId": "zetachain", @@ -1326,7 +1357,7 @@ "from": 3068132 }, "interchainGasPaymaster": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", - "interchainSecurityModule": "0x8dfE6790DbB2Ecc1bEdb0eECfc1Ff467Ae5d8C89", + "interchainSecurityModule": "0x8fB4297373f1f11032856693Cf6eee5eC8d7FF4F", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0xE2ee936bEa8e42671c400aC96dE198E06F2bA2A6", "name": "zetachain", @@ -1336,6 +1367,7 @@ "symbol": "ZETA" }, "pausableHook": "0xA1ac41d8A663fd317cc3BD94C7de92dC4BA4a882", + "pausableIsm": "0x7b75b29caD47e10146e29BBf7BD9025e021a7023", "protocol": "ethereum", "protocolFee": "0xea820f9BCFD5E16a0dd42071EB61A29874Ad81A4", "proxyAdmin": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", @@ -1351,6 +1383,7 @@ } ], "staticAggregationHookFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", + "staticAggregationIsm": "0x7CB2dbE36aF0C0893B1B3502358Bc3697343559c", "staticAggregationIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", "staticMerkleRootMultisigIsmFactory": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", "staticMessageIdMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", diff --git a/typescript/cli/src/avs/stakeRegistry.ts b/typescript/cli/src/avs/stakeRegistry.ts index d1bdd0716..42dfd4755 100644 --- a/typescript/cli/src/avs/stakeRegistry.ts +++ b/typescript/cli/src/avs/stakeRegistry.ts @@ -119,7 +119,7 @@ async function readOperatorFromEncryptedJson( message: 'Enter the password for the operator key file: ', }); - return await Wallet.fromEncryptedJson(encryptedJson, keyFilePassword); + return Wallet.fromEncryptedJson(encryptedJson, keyFilePassword); } async function getOperatorSignature( diff --git a/typescript/cli/src/commands/core.ts b/typescript/cli/src/commands/core.ts index 91b7f43b1..64c6de52e 100644 --- a/typescript/cli/src/commands/core.ts +++ b/typescript/cli/src/commands/core.ts @@ -120,7 +120,7 @@ export const read: CommandModuleWithContext<{ config: string; }> = { command: 'read', - describe: 'Reads onchain ISM & Hook configurations for given addresses', + describe: 'Reads onchain Core configuration for a given mailbox address', builder: { chain: { ...chainCommandOption, diff --git a/typescript/cli/src/commands/hook.ts b/typescript/cli/src/commands/hook.ts new file mode 100644 index 000000000..986b0d2be --- /dev/null +++ b/typescript/cli/src/commands/hook.ts @@ -0,0 +1,49 @@ +import { CommandModule } from 'yargs'; + +import { CommandModuleWithContext } from '../context/types.js'; +import { readHookConfig } from '../hook/read.js'; +import { log, logGray } from '../logger.js'; + +import { + addressCommandOption, + chainCommandOption, + outputFileCommandOption, +} from './options.js'; + +/** + * Parent command + */ +export const hookCommand: CommandModule = { + command: 'hook', + describe: 'Operations relating to Hooks', + builder: (yargs) => yargs.command(read).version(false).demandCommand(), + handler: () => log('Command required'), +}; + +// Examples for testing: +// Fallback routing hook on polygon (may take 5s): +// hyperlane hook read --chain polygon --address 0xca4cCe24E7e06241846F5EA0cda9947F0507C40C +// IGP hook on inevm (may take 5s): +// hyperlane hook read --chain inevm --address 0x19dc38aeae620380430C200a6E990D5Af5480117 +export const read: CommandModuleWithContext<{ + chain: string; + address: string; + out: string; +}> = { + command: 'read', + describe: 'Reads onchain Hook configuration for a given address', + builder: { + chain: { + ...chainCommandOption, + demandOption: true, + }, + address: addressCommandOption('Address of the Hook to read.', true), + out: outputFileCommandOption(), + }, + handler: async (args) => { + logGray('Hyperlane Hook Read'); + logGray('------------------'); + await readHookConfig(args); + process.exit(0); + }, +}; diff --git a/typescript/cli/src/commands/ism.ts b/typescript/cli/src/commands/ism.ts new file mode 100644 index 000000000..c4363ec82 --- /dev/null +++ b/typescript/cli/src/commands/ism.ts @@ -0,0 +1,54 @@ +import { CommandModule } from 'yargs'; + +import { CommandModuleWithContext } from '../context/types.js'; +import { readIsmConfig } from '../ism/read.js'; +import { log, logGray } from '../logger.js'; + +import { + addressCommandOption, + chainCommandOption, + outputFileCommandOption, +} from './options.js'; + +/** + * Parent command + */ +export const ismCommand: CommandModule = { + command: 'ism', + describe: 'Operations relating to ISMs', + builder: (yargs) => yargs.command(read).version(false).demandCommand(), + handler: () => log('Command required'), +}; + +// Examples for testing: +// Top-level aggregation ISM on celo (may take 10s) +// hyperlane ism read --chain celo --address 0x99e8E56Dce3402D6E09A82718937fc1cA2A9491E +// Aggregation ISM for bsc domain on inevm (may take 5s) +// hyperlane ism read --chain inevm --address 0x79A7c7Fe443971CBc6baD623Fdf8019C379a7178 +// Test ISM on alfajores testnet +// hyperlane ism read --chain alfajores --address 0xdB52E4853b6A40D2972E6797E0BDBDb3eB761966 +export const read: CommandModuleWithContext<{ + chain: string; + address: string; + out: string; +}> = { + command: 'read', + describe: 'Reads onchain ISM configuration for a given address', + builder: { + chain: { + ...chainCommandOption, + demandOption: true, + }, + address: addressCommandOption( + 'Address of the Interchain Security Module to read.', + true, + ), + out: outputFileCommandOption(), + }, + handler: async (argv) => { + logGray('Hyperlane ISM Read'); + logGray('------------------'); + await readIsmConfig(argv); + process.exit(0); + }, +}; diff --git a/typescript/cli/src/validator/address.ts b/typescript/cli/src/validator/address.ts index d816fcb1f..cc13738db 100644 --- a/typescript/cli/src/validator/address.ts +++ b/typescript/cli/src/validator/address.ts @@ -24,7 +24,7 @@ export async function getValidatorAddress({ region?: string; bucket?: string; keyId?: string; -}) { +}): Promise { if (!bucket && !keyId) { throw new Error('Must provide either an S3 bucket or a KMS Key ID.'); } @@ -38,7 +38,7 @@ export async function getValidatorAddress({ assert(secretKey, 'No secret access key set.'); assert(region, 'No AWS region set.'); - let validatorAddress; + let validatorAddress: string; if (bucket) { validatorAddress = await getAddressFromBucket( bucket, @@ -68,7 +68,7 @@ async function getAddressFromBucket( accessKeyId: string, secretAccessKey: string, region: string, -) { +): Promise { const s3Client = new S3Client({ region: region, credentials: { @@ -101,7 +101,7 @@ async function getAddressFromKey( accessKeyId: string, secretAccessKey: string, region: string, -) { +): Promise { const client = new KMSClient({ region: region, credentials: { @@ -138,28 +138,28 @@ function getEthereumAddress(publicKey: Buffer): string { return `0x${address.slice(-40)}`; // take last 20 bytes as ethereum address } -async function getAccessKeyId(skipConfirmation: boolean) { +async function getAccessKeyId(skipConfirmation: boolean): Promise { if (skipConfirmation) throw new Error('No AWS access key ID set.'); else - return await input({ + return input({ message: 'Please enter AWS access key ID or use the AWS_ACCESS_KEY_ID environment variable.', }); } -async function getSecretAccessKey(skipConfirmation: boolean) { +async function getSecretAccessKey(skipConfirmation: boolean): Promise { if (skipConfirmation) throw new Error('No AWS secret access key set.'); else - return await input({ + return input({ message: 'Please enter AWS secret access key or use the AWS_SECRET_ACCESS_KEY environment variable.', }); } -async function getRegion(skipConfirmation: boolean) { +async function getRegion(skipConfirmation: boolean): Promise { if (skipConfirmation) throw new Error('No AWS region set.'); else - return await input({ + return input({ message: 'Please enter AWS region or use the AWS_REGION environment variable.', }); diff --git a/typescript/sdk/src/ism/metadata/multisig.ts b/typescript/sdk/src/ism/metadata/multisig.ts index afb6539b0..19aa957d0 100644 --- a/typescript/sdk/src/ism/metadata/multisig.ts +++ b/typescript/sdk/src/ism/metadata/multisig.ts @@ -220,7 +220,15 @@ export class MultisigMetadataBuilder implements MetadataBuilder { return toHexString(buf); } - static decodeSimplePrefix(metadata: string) { + static decodeSimplePrefix(metadata: string): { + signatureOffset: number; + type: IsmType; + checkpoint: { + root: string; + index: number; + merkle_tree_hook_address: string; + }; + } { const buf = fromHexString(metadata); const merkleTree = toHexString(buf.subarray(0, 32)); const root = toHexString(buf.subarray(32, 64)); @@ -251,7 +259,16 @@ export class MultisigMetadataBuilder implements MetadataBuilder { return toHexString(buf); } - static decodeProofPrefix(metadata: string) { + static decodeProofPrefix(metadata: string): { + signatureOffset: number; + type: IsmType; + checkpoint: { + root: string; + index: number; + merkle_tree_hook_address: string; + }; + proof: MerkleProof; + } { const buf = fromHexString(metadata); const merkleTree = toHexString(buf.subarray(0, 32)); const messageIndex = buf.readUint32BE(32); diff --git a/typescript/sdk/src/providers/SmartProvider/SmartProvider.ts b/typescript/sdk/src/providers/SmartProvider/SmartProvider.ts index a5daf9ea0..9b6ab1cfb 100644 --- a/typescript/sdk/src/providers/SmartProvider/SmartProvider.ts +++ b/typescript/sdk/src/providers/SmartProvider/SmartProvider.ts @@ -97,7 +97,7 @@ export class HyperlaneSmartProvider this.supportedMethods = [...supportedMethods.values()]; } - async getPriorityFee() { + async getPriorityFee(): Promise { try { return BigNumber.from(await this.perform('maxPriorityFeePerGas', {})); } catch (error) { diff --git a/typescript/sdk/src/token/adapters/EvmTokenAdapter.ts b/typescript/sdk/src/token/adapters/EvmTokenAdapter.ts index 4d549b865..885cdad2f 100644 --- a/typescript/sdk/src/token/adapters/EvmTokenAdapter.ts +++ b/typescript/sdk/src/token/adapters/EvmTokenAdapter.ts @@ -305,7 +305,7 @@ export class EvmHypXERC20LockboxAdapter ); } - async getMintLimit() { + async getMintLimit(): Promise { const xERC20 = await this.hypXERC20Lockbox.xERC20(); const limit = await IXERC20__factory.connect( @@ -316,7 +316,7 @@ export class EvmHypXERC20LockboxAdapter return BigInt(limit.toString()); } - async getBurnLimit() { + async getBurnLimit(): Promise { const xERC20 = await this.hypXERC20Lockbox.xERC20(); const limit = await IXERC20__factory.connect( @@ -348,7 +348,7 @@ export class EvmHypXERC20Adapter ); } - async getMintLimit() { + async getMintLimit(): Promise { const xERC20 = await this.hypXERC20.wrappedToken(); const limit = await IXERC20__factory.connect( @@ -359,7 +359,7 @@ export class EvmHypXERC20Adapter return BigInt(limit.toString()); } - async getBurnLimit() { + async getBurnLimit(): Promise { const xERC20 = await this.hypXERC20.wrappedToken(); const limit = await IXERC20__factory.connect( diff --git a/typescript/sdk/src/token/config.ts b/typescript/sdk/src/token/config.ts index f12d1e77a..a89264ee6 100644 --- a/typescript/sdk/src/token/config.ts +++ b/typescript/sdk/src/token/config.ts @@ -18,7 +18,7 @@ export const CollateralExtensions = [ TokenType.collateralVault, ]; -export const gasOverhead = (tokenType: TokenType) => { +export const gasOverhead = (tokenType: TokenType): number => { switch (tokenType) { case TokenType.fastSynthetic: case TokenType.synthetic: From cd419c98a357fb8ebb64206851a7df8de4371d70 Mon Sep 17 00:00:00 2001 From: Mohammed Hussan Date: Wed, 19 Jun 2024 11:48:23 +0100 Subject: [PATCH 43/73] feat(cli): CLI command with pre-flight checks for a validator (#3966) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Description - Add validator preFlightCheck command that requires a chain name and a comma separated list of validator addresses - command will check if the validator has announced and log it's storage location and latest signed checkpoint index example usage: Happy path ``` $yarn hyperlane validator preflightCheck --chain ethereum --validators 0xCE327111035dd38698c92c3778884dBbB0ca8103,0x5AEd2fD5cC5F9749C455646C86B0Db6126CaFcbB Hyperlane CLI Latest check point index of incremental merkle tree: 6279 ✅ Validator 0xCE327111035dd38698c92c3778884dBbB0ca8103 announced storage location: s3://hyperlane-v3-validator-signatures-everstake-ethereum/us-east-2 latest checkpoint index: 6279 ✅ Validator is signing the latest available checkpoint ✅ Validator 0x5AEd2fD5cC5F9749C455646C86B0Db6126CaFcbB announced storage location: s3://hyperlane-hashkey-cloud-validator-mainnet/ap-southeast-1 latest checkpoint index: 6279 ✅ Validator is signing the latest available checkpoint ✅ Validator pre flight check passed ``` unhappy path ``` $yarn hyperlane validator preflightCheck --chain ethereum --validators 0x87cF8A85465118AfF9Ec728CA157798201B1E368,0x68970726568F09422B4c988Ee03526F3993a702b Hyperlane CLI Latest check point index of incremental merkle tree: 6279 ✅ Validator 0x87cF8A85465118AfF9Ec728CA157798201B1E368 announced storage location: s3://rc-mainnet3-ethereum-validator-2/us-east-1 latest checkpoint index: 537 ❌ Validator is not signing the latest available checkpoint ❌ Validator 0x68970726568F09422B4c988Ee03526F3993a702b has not been announced ❌ Pre flight check failed: Some validators have not been announced. Some validators are not signing the latest available checkpoint ``` ### Drive-by changes - use preferred `errorRed` method in create agent config script ### Related issues - Fixes #[3813] ### Backward compatibility Yes ### Testing Manual --- .changeset/hip-melons-build.md | 5 + typescript/cli/src/commands/options.ts | 11 ++ typescript/cli/src/commands/validator.ts | 74 ++++++++++- typescript/cli/src/config/agent.ts | 4 +- .../cli/src/validator/preFlightCheck.ts | 117 ++++++++++++++++++ 5 files changed, 207 insertions(+), 4 deletions(-) create mode 100644 .changeset/hip-melons-build.md create mode 100644 typescript/cli/src/validator/preFlightCheck.ts diff --git a/.changeset/hip-melons-build.md b/.changeset/hip-melons-build.md new file mode 100644 index 000000000..c775deeb3 --- /dev/null +++ b/.changeset/hip-melons-build.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/cli': minor +--- + +Add a validator preFlightCheck command verifying that the validator has been announced for a given chain diff --git a/typescript/cli/src/commands/options.ts b/typescript/cli/src/commands/options.ts index 3726ecb71..2ef4eb8e1 100644 --- a/typescript/cli/src/commands/options.ts +++ b/typescript/cli/src/commands/options.ts @@ -8,6 +8,11 @@ import { ENV } from '../utils/env.js'; /* Global options */ +export const demandOption = (option: Options): Options => ({ + ...option, + demandOption: true, +}); + export const logFormatCommandOption: Options = { type: 'string', description: 'Log output format', @@ -150,6 +155,12 @@ export const chainCommandOption: Options = { description: 'The specific chain to perform operations with.', }; +export const validatorCommandOption: Options = { + type: 'string', + description: 'Comma separated list of validator addresses', + demandOption: true, +}; + export const addressCommandOption = ( description: string, demandOption = false, diff --git a/typescript/cli/src/commands/validator.ts b/typescript/cli/src/commands/validator.ts index 973c0cd25..a065e7acb 100644 --- a/typescript/cli/src/commands/validator.ts +++ b/typescript/cli/src/commands/validator.ts @@ -1,8 +1,16 @@ import { CommandModule } from 'yargs'; +import { + Address, + ProtocolType, + isValidAddressEvm, + normalizeAddressEvm, +} from '@hyperlane-xyz/utils'; + import { CommandModuleWithContext } from '../context/types.js'; -import { log } from '../logger.js'; +import { errorRed, log } from '../logger.js'; import { getValidatorAddress } from '../validator/address.js'; +import { checkValidatorSetup } from '../validator/preFlightCheck.js'; import { awsAccessKeyCommandOption, @@ -10,13 +18,20 @@ import { awsKeyIdCommandOption, awsRegionCommandOption, awsSecretKeyCommandOption, + chainCommandOption, + demandOption, + validatorCommandOption, } from './options.js'; // Parent command to help configure and set up Hyperlane validators export const validatorCommand: CommandModule = { command: 'validator', describe: 'Configure and manage Hyperlane validators', - builder: (yargs) => yargs.command(addressCommand).demandCommand(), + builder: (yargs) => + yargs + .command(addressCommand) + .command(preFlightCheckCommand) + .demandCommand(), handler: () => log('Command required'), }; @@ -49,3 +64,58 @@ const addressCommand: CommandModuleWithContext<{ process.exit(0); }, }; + +const preFlightCheckCommand: CommandModuleWithContext<{ + chain: string; + validators: string; +}> = { + command: 'check', + describe: 'Check the validator has announced correctly for a given chain', + builder: { + chain: demandOption(chainCommandOption), + validators: validatorCommandOption, + }, + handler: async ({ context, chain, validators }) => { + const { multiProvider } = context; + + // validate chain + if (!multiProvider.hasChain(chain)) { + errorRed( + `❌ No metadata found for ${chain}. Ensure it is included in your configured registry.`, + ); + process.exit(1); + } + + const chainMetadata = multiProvider.getChainMetadata(chain); + + if (chainMetadata.protocol !== ProtocolType.Ethereum) { + errorRed( + `\n❌ Validator pre flight check only supports EVM chains. Exiting.`, + ); + process.exit(1); + } + + // validate validators addresses + const validatorList = validators.split(','); + const invalidAddresses: Set
= new Set(); + const validAddresses: Set
= new Set(); + + for (const address of validatorList) { + if (isValidAddressEvm(address)) { + validAddresses.add(normalizeAddressEvm(address)); + } else { + invalidAddresses.add(address); + } + } + + if (invalidAddresses.size > 0) { + errorRed( + `❌ Invalid addresses: ${Array.from(invalidAddresses).join(', ')}`, + ); + process.exit(1); + } + + await checkValidatorSetup(context, chain, validAddresses); + process.exit(0); + }, +}; diff --git a/typescript/cli/src/config/agent.ts b/typescript/cli/src/config/agent.ts index a9d1be46f..d4b59d68f 100644 --- a/typescript/cli/src/config/agent.ts +++ b/typescript/cli/src/config/agent.ts @@ -10,7 +10,7 @@ import { import { objMap, promiseObjAll } from '@hyperlane-xyz/utils'; import { CommandContext } from '../context/types.js'; -import { logBlue, logGreen, logRed } from '../logger.js'; +import { errorRed, logBlue, logGreen, logRed } from '../logger.js'; import { writeYamlOrJson } from '../utils/files.js'; export async function createAgentConfig({ @@ -61,7 +61,7 @@ export async function createAgentConfig({ try { AgentConfigSchema.parse(agentConfig); } catch (e) { - logRed( + errorRed( `Agent config is invalid, this is possibly due to required contracts not being deployed. See details below:\n${fromError( e, ).toString()}`, diff --git a/typescript/cli/src/validator/preFlightCheck.ts b/typescript/cli/src/validator/preFlightCheck.ts new file mode 100644 index 000000000..ca0c4d850 --- /dev/null +++ b/typescript/cli/src/validator/preFlightCheck.ts @@ -0,0 +1,117 @@ +import { MerkleTreeHook__factory } from '@hyperlane-xyz/core'; +import { HyperlaneCore, S3Validator } from '@hyperlane-xyz/sdk'; +import { Address } from '@hyperlane-xyz/utils'; + +import { CommandContext } from '../context/types.js'; +import { errorRed, logBlue, logGreen, warnYellow } from '../logger.js'; + +export const checkValidatorSetup = async ( + context: CommandContext, + chain: string, + validators: Set
, +) => { + const { multiProvider, registry } = context; + + const addresses = await registry.getAddresses(); + + const core = HyperlaneCore.fromAddressesMap(addresses, multiProvider); + + const validatorAnnounce = core.getContracts(chain).validatorAnnounce; + const merkleTreeHook = MerkleTreeHook__factory.connect( + addresses[chain].merkleTreeHook, + multiProvider.getProvider(chain), + ); + + let merkleTreeLatestCheckpointIndex: number | undefined; + try { + const [_, latestCheckpointIndex] = await merkleTreeHook.latestCheckpoint(); + + merkleTreeLatestCheckpointIndex = latestCheckpointIndex; + logBlue( + `\nLatest checkpoint index of incremental merkle tree: ${merkleTreeLatestCheckpointIndex}\n`, + ); + } catch (err) { + warnYellow( + `❗️ Failed to fetch latest checkpoint index of merkleTreeHook on ${chain}: ${err} \n`, + ); + } + + const errorSet = new Set(); + + const validatorsArray = Array.from(validators); + let validatorStorageLocations: string[][] | undefined; + + try { + validatorStorageLocations = + await validatorAnnounce.getAnnouncedStorageLocations(validatorsArray); + } catch (e) { + errorSet.add('Failed to read announced storage locations on chain.'); + } + + if (validatorStorageLocations) { + for (let i = 0; i < validatorsArray.length; i++) { + const validator = validatorsArray[i]; + const storageLocations = validatorStorageLocations[i]; + + if (storageLocations.length === 0) { + errorRed(`❌ Validator ${validator} has not been announced\n`); + errorSet.add('Some validators have not been announced.'); + continue; + } + + const s3StorageLocation = storageLocations[0]; + + let s3Validator: S3Validator; + try { + s3Validator = await S3Validator.fromStorageLocation(s3StorageLocation); + } catch (e) { + errorRed( + `❌ Failed to fetch storage locations for validator ${validator}, this may be due to the storage location not being an S3 bucket\n\n`, + ); + errorSet.add('Failed to fetch storage locations for some validators.'); + continue; + } + + const latestCheckpointIndex = + await s3Validator.getLatestCheckpointIndex(); + + logBlue( + `✅ Validator ${validator} announced\nstorage location: ${s3StorageLocation}\nlatest checkpoint index: ${latestCheckpointIndex}`, + ); + + // check is latestCheckpointIndex is within 1% of the merkleTreeLatestCheckpointIndex + if (merkleTreeLatestCheckpointIndex) { + const diff = Math.abs( + latestCheckpointIndex - merkleTreeLatestCheckpointIndex, + ); + if (diff > merkleTreeLatestCheckpointIndex / 100) { + errorRed( + `❌ Validator is not signing the latest available checkpoint\n\n`, + ); + errorSet.add( + `Some validators are not signing the latest available checkpoint`, + ); + } else { + logBlue( + `✅ Validator is signing the latest available checkpoint\n\n`, + ); + } + } else { + warnYellow( + `❗️ Cannot compare validator checkpoint signatures to latest checkpoint in the incremental merkletree, merkletree checkpoint could not be read\n`, + ); + } + } + } + + if (errorSet.size > 0) { + errorRed( + `\n❌ Validator pre flight check failed:\n${Array.from(errorSet).join( + '\n', + )}`, + ); + process.exit(1); + } else { + logGreen(`\n✅ Validator pre flight check passed`); + } +}; From e0f226806ef261d848def5c424c8af3936b9e79f Mon Sep 17 00:00:00 2001 From: Paul Balaji Date: Wed, 19 Jun 2024 12:19:45 +0100 Subject: [PATCH 44/73] feat: implement `create()` for `EvmHookModule` (#3861) resolves https://github.com/hyperlane-xyz/issues/issues/1153 - enables creation of new Hooks through the EvmHookModule - introduce `EvmModuleDeployer` - separate from `HyperlaneDeployer` - contains some basic methods to deploy a contract/proxy - reduces module necessity HyperlaneDeployer IN PROGRESS: - [x] tests - [x] figure out why randomly generated routing/fallbackrouting hooks fail - [x] figure out why protocol fee hooks fail ![image](https://github.com/hyperlane-xyz/hyperlane-monorepo/assets/10051819/4cba7af3-4e72-49f6-8f98-fd7fea147282) --------- Signed-off-by: Paul Balaji --- .changeset/shy-countries-heal.md | 6 + typescript/cli/src/deploy/warp.ts | 6 +- .../sdk/src/deploy/EvmModuleDeployer.ts | 287 ++++++++ .../src/hook/EvmHookModule.hardhat-test.ts | 488 ++++++++++++++ typescript/sdk/src/hook/EvmHookModule.ts | 627 +++++++++++++++++- .../sdk/src/ism/EvmIsmModule.hardhat-test.ts | 78 +-- typescript/sdk/src/ism/EvmIsmModule.ts | 165 ++--- typescript/utils/src/objects.ts | 5 +- 8 files changed, 1498 insertions(+), 164 deletions(-) create mode 100644 .changeset/shy-countries-heal.md create mode 100644 typescript/sdk/src/deploy/EvmModuleDeployer.ts create mode 100644 typescript/sdk/src/hook/EvmHookModule.hardhat-test.ts diff --git a/.changeset/shy-countries-heal.md b/.changeset/shy-countries-heal.md new file mode 100644 index 000000000..295eba47f --- /dev/null +++ b/.changeset/shy-countries-heal.md @@ -0,0 +1,6 @@ +--- +'@hyperlane-xyz/sdk': minor +--- + +- Enables creation of new Hooks through the `EvmHookModule`. +- Introduces an `EvmModuleDeployer` to perform the barebones tasks of deploying contracts/proxies. diff --git a/typescript/cli/src/deploy/warp.ts b/typescript/cli/src/deploy/warp.ts index 42331b08a..707415ae1 100644 --- a/typescript/cli/src/deploy/warp.ts +++ b/typescript/cli/src/deploy/warp.ts @@ -8,7 +8,6 @@ import { HypERC721Deployer, HyperlaneAddresses, HyperlaneContractsMap, - HyperlaneDeployer, HyperlaneProxyFactoryDeployer, MultiProvider, TOKEN_TYPE_TO_STANDARD, @@ -196,7 +195,6 @@ async function deployAndResolveWarpIsm( chain, warpConfig, multiProvider, - ismFactoryDeployer, { domainRoutingIsmFactory: chainAddresses.domainRoutingIsmFactory, staticAggregationHookFactory: @@ -227,7 +225,6 @@ async function createWarpIsm( chain: string, warpConfig: WarpRouteDeployConfig, multiProvider: MultiProvider, - ismFactoryDeployer: HyperlaneDeployer, factoryAddresses: HyperlaneAddresses, ): Promise { const { @@ -240,9 +237,8 @@ async function createWarpIsm( const evmIsmModule = await EvmIsmModule.create({ chain, multiProvider, - deployer: ismFactoryDeployer, mailbox: warpConfig[chain].mailbox, - factories: { + proxyFactoryFactories: { domainRoutingIsmFactory, staticAggregationHookFactory, staticAggregationIsmFactory, diff --git a/typescript/sdk/src/deploy/EvmModuleDeployer.ts b/typescript/sdk/src/deploy/EvmModuleDeployer.ts new file mode 100644 index 000000000..ba8a16f83 --- /dev/null +++ b/typescript/sdk/src/deploy/EvmModuleDeployer.ts @@ -0,0 +1,287 @@ +import { ethers } from 'ethers'; +import { Logger } from 'pino'; + +import { + StaticAddressSetFactory, + StaticThresholdAddressSetFactory, + TransparentUpgradeableProxy__factory, +} from '@hyperlane-xyz/core'; +import { buildArtifact as coreBuildArtifact } from '@hyperlane-xyz/core/buildArtifact.js'; +import { Address, rootLogger } from '@hyperlane-xyz/utils'; + +import { HyperlaneContracts, HyperlaneFactories } from '../contracts/types.js'; +import { MultiProvider } from '../providers/MultiProvider.js'; +import { ChainMap, ChainName } from '../types.js'; + +import { isProxy, proxyConstructorArgs } from './proxy.js'; +import { ContractVerifier } from './verify/ContractVerifier.js'; +import { + ContractVerificationInput, + ExplorerLicenseType, +} from './verify/types.js'; +import { getContractVerificationInput } from './verify/utils.js'; + +export class EvmModuleDeployer { + public verificationInputs: ChainMap = {}; + + constructor( + protected readonly multiProvider: MultiProvider, + protected readonly factories: Factories, + protected readonly logger = rootLogger.child({ + module: 'EvmModuleDeployer', + }), + protected readonly contractVerifier = new ContractVerifier( + multiProvider, + {}, + coreBuildArtifact, + ExplorerLicenseType.MIT, + ), + ) {} + + // Deploys a contract from a factory + public async deployContractFromFactory({ + chain, + factory, + contractName, + constructorArgs, + initializeArgs, + }: { + chain: ChainName; + factory: F; + contractName: string; + constructorArgs: Parameters; + initializeArgs?: Parameters>['initialize']>; + }): Promise> { + this.logger.info( + `Deploy ${contractName} on ${chain} with constructor args (${constructorArgs.join( + ', ', + )})`, + ); + const contract = await this.multiProvider.handleDeploy( + chain, + factory, + constructorArgs, + ); + + if (initializeArgs) { + this.logger.debug(`Initialize ${contractName} on ${chain}`); + const overrides = this.multiProvider.getTransactionOverrides(chain); + const initTx = await contract.initialize(...initializeArgs, overrides); + await this.multiProvider.handleTx(chain, initTx); + } + + const verificationInput = getContractVerificationInput( + contractName, + contract, + factory.bytecode, + ); + this.addVerificationArtifacts({ chain, artifacts: [verificationInput] }); + + // try verifying contract + try { + await this.contractVerifier?.verifyContract(chain, verificationInput); + } catch (error) { + // log error but keep deploying, can also verify post-deployment if needed + this.logger.debug(`Error verifying contract: ${error}`); + } + + return contract; + } + + /** + * Deploys a contract with a specified name. + * + * This function is capable of deploying any contract type defined within the `Factories` type to a specified chain. + * + * @param {ChainName} chain - The name of the chain on which the contract is to be deployed. + * @param {K} contractKey - The key identifying the factory to use for deployment. + * @param {string} contractName - The name of the contract to deploy. This must match the contract source code. + * @param {Parameters} constructorArgs - Arguments for the contract's constructor. + * @param {Parameters>['initialize']>?} initializeArgs - Optional arguments for the contract's initialization function. + * @returns {Promise[K]>} A promise that resolves to the deployed contract instance. + */ + public async deployContractWithName({ + chain, + contractKey, + contractName, + constructorArgs, + initializeArgs, + }: { + chain: ChainName; + contractKey: K; + contractName: string; + constructorArgs: Parameters; + initializeArgs?: Parameters< + Awaited>['initialize'] + >; + }): Promise[K]> { + const contract = await this.deployContractFromFactory({ + chain, + factory: this.factories[contractKey], + contractName, + constructorArgs, + initializeArgs, + }); + return contract; + } + + // Deploys a contract with the same name as the contract key + public async deployContract({ + chain, + contractKey, + constructorArgs, + initializeArgs, + }: { + chain: ChainName; + contractKey: K; + constructorArgs: Parameters; + initializeArgs?: Parameters< + Awaited>['initialize'] + >; + }): Promise[K]> { + return this.deployContractWithName({ + chain, + contractKey, + contractName: contractKey.toString(), + constructorArgs, + initializeArgs, + }); + } + + // Deploys the Implementation and Proxy for a given contract + public async deployProxiedContract({ + chain, + contractKey, + contractName, + proxyAdmin, + constructorArgs, + initializeArgs, + }: { + chain: ChainName; + contractKey: K; + contractName: string; + proxyAdmin: string; + constructorArgs: Parameters; + initializeArgs?: Parameters[K]['initialize']>; + }): Promise[K]> { + // Try to initialize the implementation even though it may not be necessary + const implementation = await this.deployContractWithName({ + chain, + contractKey, + contractName, + constructorArgs, + initializeArgs, + }); + + // Initialize the proxy the same way + return this.deployProxy({ + chain, + implementation, + proxyAdmin, + initializeArgs, + }); + } + + // Deploys a proxy for a given implementation contract + protected async deployProxy({ + chain, + implementation, + proxyAdmin, + initializeArgs, + }: { + chain: ChainName; + implementation: C; + proxyAdmin: string; + initializeArgs?: Parameters; + }): Promise { + const isProxied = await isProxy( + this.multiProvider.getProvider(chain), + implementation.address, + ); + if (isProxied) { + // if the implementation is already a proxy, do not deploy a new proxy + return implementation; + } + + const constructorArgs = proxyConstructorArgs( + implementation, + proxyAdmin, + initializeArgs, + ); + const proxy = await this.deployContractFromFactory({ + chain, + factory: new TransparentUpgradeableProxy__factory(), + contractName: 'TransparentUpgradeableProxy', + constructorArgs, + }); + + return implementation.attach(proxy.address) as C; + } + + // Adds verification artifacts to the verificationInputs map + protected addVerificationArtifacts({ + chain, + artifacts, + }: { + chain: ChainName; + artifacts: ContractVerificationInput[]; + }): void { + this.verificationInputs[chain] = this.verificationInputs[chain] || []; + artifacts.forEach((artifact) => { + this.verificationInputs[chain].push(artifact); + }); + } + + // Static deploy function used by Hook and ISM modules. + public static async deployStaticAddressSet({ + chain, + factory, + values, + logger, + threshold = values.length, + multiProvider, + }: { + chain: ChainName; + factory: StaticThresholdAddressSetFactory | StaticAddressSetFactory; + values: Address[]; + logger: Logger; + threshold?: number; + multiProvider: MultiProvider; + }): Promise
{ + const address = await factory['getAddress(address[],uint8)']( + values, + threshold, + ); + const code = await multiProvider.getProvider(chain).getCode(address); + if (code === '0x') { + logger.debug( + `Deploying new ${threshold} of ${values.length} address set to ${chain}`, + ); + const overrides = multiProvider.getTransactionOverrides(chain); + const hash = await factory['deploy(address[],uint8)']( + values, + threshold, + overrides, + ); + await multiProvider.handleTx(chain, hash); + } else { + logger.debug( + `Recovered ${threshold} of ${values.length} address set on ${chain}: ${address}`, + ); + } + + // TODO: figure out how to get the constructor arguments for manual deploy TXs + // const verificationInput = buildVerificationInput( + // NAME, + // ADDRESS, + // CONSTRUCTOR_ARGS, + // ); + // await this.deployer.verifyContract( + // this.chainName, + // verificationInput, + // logger, + // ); + + return address; + } +} diff --git a/typescript/sdk/src/hook/EvmHookModule.hardhat-test.ts b/typescript/sdk/src/hook/EvmHookModule.hardhat-test.ts new file mode 100644 index 000000000..fbc1446da --- /dev/null +++ b/typescript/sdk/src/hook/EvmHookModule.hardhat-test.ts @@ -0,0 +1,488 @@ +/* eslint-disable no-console */ +import { expect } from 'chai'; +import hre from 'hardhat'; + +import { + Address, + configDeepEquals, + normalizeConfig, + stringifyObject, +} from '@hyperlane-xyz/utils'; + +import { TestChainName, testChains } from '../consts/testChains.js'; +import { HyperlaneAddresses, HyperlaneContracts } from '../contracts/types.js'; +import { TestCoreDeployer } from '../core/TestCoreDeployer.js'; +import { CoreAddresses } from '../core/contracts.js'; +import { HyperlaneProxyFactoryDeployer } from '../deploy/HyperlaneProxyFactoryDeployer.js'; +import { ProxyFactoryFactories } from '../deploy/contracts.js'; +import { HyperlaneIsmFactory } from '../ism/HyperlaneIsmFactory.js'; +import { MultiProvider } from '../providers/MultiProvider.js'; +import { randomAddress, randomInt } from '../test/testUtils.js'; + +import { EvmHookModule } from './EvmHookModule.js'; +import { + AggregationHookConfig, + DomainRoutingHookConfig, + FallbackRoutingHookConfig, + HookConfig, + HookType, + IgpHookConfig, + MerkleTreeHookConfig, + PausableHookConfig, + ProtocolFeeHookConfig, +} from './types.js'; + +const hookTypes = Object.values(HookType); + +function randomHookType(): HookType { + // OP_STACK filtering is temporary until we have a way to deploy the required contracts + const filteredHookTypes = hookTypes.filter( + (type) => type !== HookType.OP_STACK && type !== HookType.CUSTOM, + ); + return filteredHookTypes[ + Math.floor(Math.random() * filteredHookTypes.length) + ]; +} + +function randomProtocolFee(): { maxProtocolFee: string; protocolFee: string } { + const maxProtocolFee = Math.random() * 100000000000000; + const protocolFee = (Math.random() * maxProtocolFee) / 1000; + return { + maxProtocolFee: Math.floor(maxProtocolFee).toString(), + protocolFee: Math.floor(protocolFee).toString(), + }; +} + +function randomHookConfig( + depth = 0, + maxDepth = 2, + providedHookType?: HookType, +): HookConfig { + const hookType: HookType = providedHookType ?? randomHookType(); + + if (depth >= maxDepth) { + if ( + hookType === HookType.AGGREGATION || + hookType === HookType.ROUTING || + hookType === HookType.FALLBACK_ROUTING + ) { + return { type: HookType.MERKLE_TREE }; + } + } + + switch (hookType) { + case HookType.MERKLE_TREE: + return { type: hookType }; + + case HookType.AGGREGATION: + return { + type: hookType, + hooks: [ + randomHookConfig(depth + 1, maxDepth), + randomHookConfig(depth + 1, maxDepth), + ], + }; + + case HookType.INTERCHAIN_GAS_PAYMASTER: { + const owner = randomAddress(); + return { + owner, + type: hookType, + beneficiary: randomAddress(), + oracleKey: owner, + overhead: Object.fromEntries( + testChains.map((c) => [c, Math.floor(Math.random() * 100)]), + ), + oracleConfig: Object.fromEntries( + testChains.map((c) => [ + c, + { + tokenExchangeRate: randomInt(1234567891234).toString(), + gasPrice: randomInt(1234567891234).toString(), + }, + ]), + ), + }; + } + + case HookType.PROTOCOL_FEE: { + const { maxProtocolFee, protocolFee } = randomProtocolFee(); + return { + owner: randomAddress(), + type: hookType, + maxProtocolFee, + protocolFee, + beneficiary: randomAddress(), + }; + } + + case HookType.OP_STACK: + return { + owner: randomAddress(), + type: hookType, + nativeBridge: randomAddress(), + destinationChain: 'testChain', + }; + + case HookType.ROUTING: + return { + owner: randomAddress(), + type: hookType, + domains: Object.fromEntries( + testChains.map((c) => [c, randomHookConfig(depth + 1, maxDepth)]), + ), + }; + + case HookType.FALLBACK_ROUTING: + return { + owner: randomAddress(), + type: hookType, + fallback: randomHookConfig(depth + 1, maxDepth), + domains: Object.fromEntries( + testChains.map((c) => [c, randomHookConfig(depth + 1, maxDepth)]), + ), + }; + + case HookType.PAUSABLE: + return { + owner: randomAddress(), + type: hookType, + paused: false, + }; + + default: + throw new Error(`Unsupported Hook type: ${hookType}`); + } +} + +describe('EvmHookModule', async () => { + let multiProvider: MultiProvider; + let coreAddresses: CoreAddresses; + + const chain = TestChainName.test4; + let proxyFactoryAddresses: HyperlaneAddresses; + let factoryContracts: HyperlaneContracts; + + beforeEach(async () => { + const [signer] = await hre.ethers.getSigners(); + multiProvider = MultiProvider.createTestMultiProvider({ signer }); + + const ismFactoryDeployer = new HyperlaneProxyFactoryDeployer(multiProvider); + const contractsMap = await ismFactoryDeployer.deploy( + multiProvider.mapKnownChains(() => ({})), + ); + + // get addresses of factories for the chain + factoryContracts = contractsMap[chain]; + proxyFactoryAddresses = Object.keys(factoryContracts).reduce((acc, key) => { + acc[key] = + contractsMap[chain][key as keyof ProxyFactoryFactories].address; + return acc; + }, {} as Record) as HyperlaneAddresses; + + // legacy HyperlaneIsmFactory is required to do a core deploy + const legacyIsmFactory = new HyperlaneIsmFactory( + contractsMap, + multiProvider, + ); + + // core deployer for tests + const testCoreDeployer = new TestCoreDeployer( + multiProvider, + legacyIsmFactory, + ); + + // mailbox and proxy admin for the core deploy + const { mailbox, proxyAdmin, validatorAnnounce } = ( + await testCoreDeployer.deployApp() + ).getContracts(chain); + + coreAddresses = { + mailbox: mailbox.address, + proxyAdmin: proxyAdmin.address, + validatorAnnounce: validatorAnnounce.address, + }; + }); + + // Helper method for checking whether Hook module matches a given config + async function hookModuleMatchesConfig({ + hook, + config, + }: { + hook: EvmHookModule; + config: HookConfig; + }): Promise { + const normalizedDerivedConfig = normalizeConfig(await hook.read()); + const normalizedConfig = normalizeConfig(config); + const matches = configDeepEquals(normalizedDerivedConfig, normalizedConfig); + if (!matches) { + console.error( + 'Derived config:\n', + stringifyObject(normalizedDerivedConfig), + ); + console.error('Expected config:\n', stringifyObject(normalizedConfig)); + } + return matches; + } + + // hook module and config for testing + let testHook: EvmHookModule; + let testConfig: HookConfig; + + // expect that the hook matches the config after all tests + afterEach(async () => { + expect( + await hookModuleMatchesConfig({ hook: testHook, config: testConfig }), + ).to.be.true; + }); + + // create a new Hook and verify that it matches the config + async function createHook( + config: HookConfig, + ): Promise<{ hook: EvmHookModule; initialHookAddress: Address }> { + console.log('Creating hook with config: ', stringifyObject(config)); + const hook = await EvmHookModule.create({ + chain, + config, + proxyFactoryFactories: proxyFactoryAddresses, + coreAddresses, + multiProvider, + }); + testConfig = config; + testHook = hook; + return { hook, initialHookAddress: hook.serialize().deployedHook }; + } + + describe('create', async () => { + it('deploys a hook of type CUSTOM', async () => { + const config: HookConfig = randomAddress(); + await createHook(config); + }); + + it('deploys a hook of type MERKLE_TREE', async () => { + const config: MerkleTreeHookConfig = { + type: HookType.MERKLE_TREE, + }; + await createHook(config); + }); + + it('deploys a hook of type INTERCHAIN_GAS_PAYMASTER', async () => { + const owner = randomAddress(); + const config: IgpHookConfig = { + owner, + type: HookType.INTERCHAIN_GAS_PAYMASTER, + beneficiary: randomAddress(), + oracleKey: owner, + overhead: Object.fromEntries( + testChains.map((c) => [c, Math.floor(Math.random() * 100)]), + ), + oracleConfig: Object.fromEntries( + testChains.map((c) => [ + c, + { + tokenExchangeRate: randomInt(1234567891234).toString(), + gasPrice: randomInt(1234567891234).toString(), + }, + ]), + ), + }; + await createHook(config); + }); + + it('deploys a hook of type PROTOCOL_FEE', async () => { + const { maxProtocolFee, protocolFee } = randomProtocolFee(); + const config: ProtocolFeeHookConfig = { + owner: randomAddress(), + type: HookType.PROTOCOL_FEE, + maxProtocolFee, + protocolFee, + beneficiary: randomAddress(), + }; + await createHook(config); + }); + + it('deploys a hook of type ROUTING', async () => { + const config: DomainRoutingHookConfig = { + owner: randomAddress(), + type: HookType.ROUTING, + domains: Object.fromEntries( + testChains + .filter((c) => c !== TestChainName.test4) + .map((c) => [ + c, + { + type: HookType.MERKLE_TREE, + }, + ]), + ), + }; + await createHook(config); + }); + + it('deploys a hook of type FALLBACK_ROUTING', async () => { + const config: FallbackRoutingHookConfig = { + owner: randomAddress(), + type: HookType.FALLBACK_ROUTING, + fallback: { type: HookType.MERKLE_TREE }, + domains: Object.fromEntries( + testChains + .filter((c) => c !== TestChainName.test4) + .map((c) => [ + c, + { + type: HookType.MERKLE_TREE, + }, + ]), + ), + }; + await createHook(config); + }); + + it('deploys a hook of type AGGREGATION', async () => { + const config: AggregationHookConfig = { + type: HookType.AGGREGATION, + hooks: [{ type: HookType.MERKLE_TREE }, { type: HookType.MERKLE_TREE }], + }; + await createHook(config); + }); + + it('deploys a hook of type PAUSABLE', async () => { + const config: PausableHookConfig = { + owner: randomAddress(), + type: HookType.PAUSABLE, + paused: false, + }; + await createHook(config); + }); + + // it('deploys a hook of type OP_STACK', async () => { + // need to setup deploying/mocking IL1CrossDomainMessenger before this test can be enabled + // const config: OpStackHookConfig = { + // owner: randomAddress(), + // type: HookType.OP_STACK, + // nativeBridge: randomAddress(), + // destinationChain: 'testChain', + // }; + // await createHook(config); + // }); + + for (let i = 0; i < 16; i++) { + it(`deploys a random hook config #${i}`, async () => { + // random config with depth 0-2 + const config = randomHookConfig(); + await createHook(config); + }); + } + + it('regression test #1', async () => { + const config: HookConfig = { + type: HookType.AGGREGATION, + hooks: [ + { + owner: '0xebe67f0a423fd1c4af21debac756e3238897c665', + type: HookType.INTERCHAIN_GAS_PAYMASTER, + beneficiary: '0xfe3be5940327305aded56f20359761ef85317554', + oracleKey: '0xebe67f0a423fd1c4af21debac756e3238897c665', + overhead: { + test1: 18, + test2: 85, + test3: 23, + test4: 69, + }, + oracleConfig: { + test1: { + tokenExchangeRate: '1032586497157', + gasPrice: '1026942205817', + }, + test2: { + tokenExchangeRate: '81451154935', + gasPrice: '1231220057593', + }, + test3: { + tokenExchangeRate: '31347320275', + gasPrice: '21944956734', + }, + test4: { + tokenExchangeRate: '1018619796544', + gasPrice: '1124484183261', + }, + }, + }, + { + owner: '0xcc803fc9e6551b9eaaebfabbdd5af3eccea252ff', + type: HookType.ROUTING, + domains: { + test1: { + type: HookType.MERKLE_TREE, + }, + test2: { + owner: '0x7e43dfa88c4a5d29a8fcd69883b7f6843d465ca3', + type: HookType.INTERCHAIN_GAS_PAYMASTER, + beneficiary: '0x762e71a849a3825613cf5cbe70bfff27d0fe7766', + oracleKey: '0x7e43dfa88c4a5d29a8fcd69883b7f6843d465ca3', + overhead: { + test1: 46, + test2: 34, + test3: 47, + test4: 24, + }, + oracleConfig: { + test1: { + tokenExchangeRate: '1132883204938', + gasPrice: '1219466305935', + }, + test2: { + tokenExchangeRate: '938422264723', + gasPrice: '229134538568', + }, + test3: { + tokenExchangeRate: '69699594189', + gasPrice: '475781234236', + }, + test4: { + tokenExchangeRate: '1027245678936', + gasPrice: '502686418976', + }, + }, + }, + test3: { + type: HookType.MERKLE_TREE, + }, + test4: { + owner: '0xa1ce72b70566f2cba6000bfe6af50f0f358f49d7', + type: HookType.INTERCHAIN_GAS_PAYMASTER, + beneficiary: '0x9796c0c49c61fe01eb1a8ba56d09b831f6da8603', + oracleKey: '0xa1ce72b70566f2cba6000bfe6af50f0f358f49d7', + overhead: { + test1: 71, + test2: 16, + test3: 37, + test4: 13, + }, + oracleConfig: { + test1: { + tokenExchangeRate: '443874625350', + gasPrice: '799154764503', + }, + test2: { + tokenExchangeRate: '915348561750', + gasPrice: '1124345797215', + }, + test3: { + tokenExchangeRate: '930832717805', + gasPrice: '621743941770', + }, + test4: { + tokenExchangeRate: '147394981623', + gasPrice: '766494385983', + }, + }, + }, + }, + }, + ], + }; + await createHook(config); + }); + }); +}); diff --git a/typescript/sdk/src/hook/EvmHookModule.ts b/typescript/sdk/src/hook/EvmHookModule.ts index 517666f4e..29ea95534 100644 --- a/typescript/sdk/src/hook/EvmHookModule.ts +++ b/typescript/sdk/src/hook/EvmHookModule.ts @@ -1,45 +1,116 @@ -import { Address, ProtocolType, rootLogger } from '@hyperlane-xyz/utils'; +import { BigNumber, ethers } from 'ethers'; +import { + DomainRoutingHook, + DomainRoutingHook__factory, + FallbackDomainRoutingHook, + IL1CrossDomainMessenger__factory, + IPostDispatchHook__factory, + InterchainGasPaymaster, + OPStackHook, + OPStackIsm__factory, + PausableHook, + ProtocolFee, + StaticAggregationHook, + StaticAggregationHookFactory__factory, + StaticAggregationHook__factory, + StorageGasOracle, +} from '@hyperlane-xyz/core'; +import { + Address, + ProtocolType, + addressToBytes32, + configDeepEquals, + rootLogger, +} from '@hyperlane-xyz/utils'; + +import { TOKEN_EXCHANGE_RATE_SCALE } from '../consts/igp.js'; import { HyperlaneAddresses } from '../contracts/types.js'; import { HyperlaneModule, HyperlaneModuleParams, } from '../core/AbstractHyperlaneModule.js'; -import { HyperlaneDeployer } from '../deploy/HyperlaneDeployer.js'; +import { CoreAddresses } from '../core/contracts.js'; +import { EvmModuleDeployer } from '../deploy/EvmModuleDeployer.js'; +import { ProxyFactoryFactories } from '../deploy/contracts.js'; +import { ContractVerifier } from '../deploy/verify/ContractVerifier.js'; +import { IgpFactories, igpFactories } from '../gas/contracts.js'; +import { IgpConfig } from '../gas/types.js'; +import { EvmIsmModule } from '../ism/EvmIsmModule.js'; +import { IsmType, OpStackIsmConfig } from '../ism/types.js'; import { MultiProvider } from '../providers/MultiProvider.js'; import { AnnotatedEV5Transaction } from '../providers/ProviderType.js'; +import { ChainNameOrId } from '../types.js'; import { EvmHookReader } from './EvmHookReader.js'; -import { HookFactories } from './contracts.js'; -import { HookConfig } from './types.js'; +import { DeployedHook, HookFactories, hookFactories } from './contracts.js'; +import { + AggregationHookConfig, + DomainRoutingHookConfig, + FallbackRoutingHookConfig, + HookConfig, + HookType, + IgpHookConfig, + OpStackHookConfig, + PausableHookConfig, + ProtocolFeeHookConfig, +} from './types.js'; + +type HookModuleAddresses = { + deployedHook: Address; + mailbox: Address; + proxyAdmin: Address; +}; -// WIP example implementation of EvmHookModule export class EvmHookModule extends HyperlaneModule< ProtocolType.Ethereum, HookConfig, - HyperlaneAddresses & { - deployedHook: Address; - } + HyperlaneAddresses & HookModuleAddresses > { - protected logger = rootLogger.child({ module: 'EvmHookModule' }); - protected reader: EvmHookReader; + protected readonly logger = rootLogger.child({ module: 'EvmHookModule' }); + protected readonly reader: EvmHookReader; + protected readonly deployer: EvmModuleDeployer; + + // Adding these to reduce how often we need to grab from MultiProvider. + public readonly chain: string; + // We use domainId here because MultiProvider.getDomainId() will always + // return a number, and EVM the domainId and chainId are the same. + public readonly domainId: number; + + // Transaction overrides for the chain + protected readonly txOverrides: Partial; protected constructor( protected readonly multiProvider: MultiProvider, - protected readonly deployer: HyperlaneDeployer, args: HyperlaneModuleParams< HookConfig, - HyperlaneAddresses & { - deployedHook: Address; - } + HyperlaneAddresses & HookModuleAddresses >, + contractVerifier?: ContractVerifier, ) { super(args); - this.reader = new EvmHookReader(multiProvider, args.chain); + + this.reader = new EvmHookReader(multiProvider, this.args.chain); + this.deployer = new EvmModuleDeployer( + multiProvider, + { + ...hookFactories, + ...igpFactories, + }, + this.logger, + contractVerifier, + ); + + this.chain = this.multiProvider.getChainName(this.args.chain); + this.domainId = this.multiProvider.getDomainId(this.chain); + + this.txOverrides = this.multiProvider.getTransactionOverrides(this.chain); } public async read(): Promise { - return this.reader.deriveHookConfig(this.args.addresses.deployedHook); + return typeof this.args.config === 'string' + ? this.args.addresses.deployedHook + : this.reader.deriveHookConfig(this.args.addresses.deployedHook); } public async update(_config: HookConfig): Promise { @@ -47,7 +118,527 @@ export class EvmHookModule extends HyperlaneModule< } // manually write static create function - public static create(_config: HookConfig): Promise { - throw new Error('not implemented'); + public static async create({ + chain, + config, + proxyFactoryFactories, + coreAddresses, + multiProvider, + }: { + chain: ChainNameOrId; + config: HookConfig; + proxyFactoryFactories: HyperlaneAddresses; + coreAddresses: CoreAddresses; + multiProvider: MultiProvider; + }): Promise { + // instantiate new EvmHookModule + const module = new EvmHookModule(multiProvider, { + addresses: { + ...proxyFactoryFactories, + ...coreAddresses, + deployedHook: ethers.constants.AddressZero, + }, + chain, + config, + }); + + // deploy hook and assign address to module + const deployedHook = await module.deploy({ config }); + module.args.addresses.deployedHook = deployedHook.address; + + return module; + } + + // Compute delta between current and target domain configurations + protected async computeRoutingHooksToSet({ + currentDomains, + targetDomains, + }: { + currentDomains: DomainRoutingHookConfig['domains']; + targetDomains: DomainRoutingHookConfig['domains']; + }): Promise { + const routingHookUpdates: DomainRoutingHook.HookConfigStruct[] = []; + + // Iterate over the target domains and compare with the current configuration + for (const [dest, targetDomainConfig] of Object.entries(targetDomains)) { + const destDomain = this.multiProvider.tryGetDomainId(dest); + if (!destDomain) { + this.logger.warn(`Domain not found in MultiProvider: ${dest}`); + continue; + } + + // If the domain is not in the current config or the config has changed, deploy a new hook + // TODO: in-place updates per domain as a future optimization + if (!configDeepEquals(currentDomains[dest], targetDomainConfig)) { + const domainHook = await this.deploy({ + config: targetDomainConfig, + }); + + routingHookUpdates.push({ + destination: destDomain, + hook: domainHook.address, + }); + } + } + + return routingHookUpdates; + } + + // Updates a routing hook + protected async updateRoutingHook({ + current, + target, + }: { + current: DomainRoutingHookConfig | FallbackRoutingHookConfig; + target: DomainRoutingHookConfig | FallbackRoutingHookConfig; + }): Promise { + // Deploy a new fallback hook if the fallback config has changed + if ( + target.type === HookType.FALLBACK_ROUTING && + !configDeepEquals( + target.fallback, + (current as FallbackRoutingHookConfig).fallback, + ) + ) { + const hook = await this.deploy({ config: target }); + this.args.addresses.deployedHook = hook.address; + } + + const routingUpdates = await this.computeRoutingHooksToSet({ + currentDomains: current.domains, + targetDomains: target.domains, + }); + + // Return if no updates are required + if (routingUpdates.length === 0) { + return []; + } + + // Create tx for setting hooks + return [ + { + annotation: 'Updating routing hooks...', + chainId: this.domainId, + to: this.args.addresses.deployedHook, + data: DomainRoutingHook__factory.createInterface().encodeFunctionData( + 'setHooks', + [routingUpdates], + ), + }, + ]; + } + + protected async deploy({ + config, + }: { + config: HookConfig; + }): Promise { + // If it's an address, just return a base Hook + if (typeof config === 'string') { + // TODO: https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/3773 + // we can remove the ts-ignore once we have a proper type for address Hooks + // @ts-ignore + return IPostDispatchHook__factory.connect( + config, + this.multiProvider.getSignerOrProvider(this.args.chain), + ); + } + + switch (config.type) { + case HookType.MERKLE_TREE: + return this.deployer.deployContract({ + chain: this.chain, + contractKey: HookType.MERKLE_TREE, + constructorArgs: [this.args.addresses.mailbox], + }); + case HookType.INTERCHAIN_GAS_PAYMASTER: + return this.deployIgpHook({ config }); + case HookType.AGGREGATION: + return this.deployAggregationHook({ config }); + case HookType.PROTOCOL_FEE: + return this.deployProtocolFeeHook({ config }); + case HookType.OP_STACK: + return this.deployOpStackHook({ config }); + case HookType.ROUTING: + case HookType.FALLBACK_ROUTING: + return this.deployRoutingHook({ config }); + case HookType.PAUSABLE: { + return this.deployPausableHook({ config }); + } + default: + throw new Error(`Unsupported hook config: ${config}`); + } + } + + protected async deployProtocolFeeHook({ + config, + }: { + config: ProtocolFeeHookConfig; + }): Promise { + this.logger.debug('Deploying ProtocolFeeHook...'); + return this.deployer.deployContract({ + chain: this.chain, + contractKey: HookType.PROTOCOL_FEE, + constructorArgs: [ + config.maxProtocolFee, + config.protocolFee, + config.beneficiary, + config.owner, + ], + }); + } + + protected async deployPausableHook({ + config, + }: { + config: PausableHookConfig; + }): Promise { + this.logger.debug('Deploying PausableHook...'); + const hook = await this.deployer.deployContract({ + chain: this.chain, + contractKey: HookType.PAUSABLE, + constructorArgs: [], + }); + + // transfer ownership + await this.multiProvider.handleTx( + this.chain, + hook.transferOwnership(config.owner, this.txOverrides), + ); + + return hook; + } + + protected async deployAggregationHook({ + config, + }: { + config: AggregationHookConfig; + }): Promise { + this.logger.debug('Deploying AggregationHook...'); + + // deploy subhooks + const aggregatedHooks = []; + for (const hookConfig of config.hooks) { + const { address } = await this.deploy({ config: hookConfig }); + aggregatedHooks.push(address); + } + + // deploy aggregation hook + this.logger.debug( + `Deploying aggregation hook of type ${config.hooks.map((h) => + typeof h === 'string' ? h : h.type, + )}...`, + ); + const signer = this.multiProvider.getSigner(this.chain); + const factory = StaticAggregationHookFactory__factory.connect( + this.args.addresses.staticAggregationHookFactory, + signer, + ); + const address = await EvmModuleDeployer.deployStaticAddressSet({ + chain: this.chain, + factory, + values: aggregatedHooks, + logger: this.logger, + multiProvider: this.multiProvider, + }); + + // return aggregation hook + return StaticAggregationHook__factory.connect(address, signer); + } + + protected async deployOpStackHook({ + config, + }: { + config: OpStackHookConfig; + }): Promise { + const chain = this.chain; + const mailbox = this.args.addresses.mailbox; + this.logger.debug( + 'Deploying OPStackHook for %s to %s...', + chain, + config.destinationChain, + ); + + // fetch l2 messenger address from l1 messenger + const l1Messenger = IL1CrossDomainMessenger__factory.connect( + config.nativeBridge, + this.multiProvider.getSignerOrProvider(chain), + ); + const l2Messenger: Address = await l1Messenger.OTHER_MESSENGER(); + // deploy opstack ism + const ismConfig: OpStackIsmConfig = { + type: IsmType.OP_STACK, + origin: chain, + nativeBridge: l2Messenger, + }; + + // deploy opstack ism + const opStackIsmAddress = ( + await EvmIsmModule.create({ + chain: config.destinationChain, + config: ismConfig, + proxyFactoryFactories: this.args.addresses, + mailbox: mailbox, + multiProvider: this.multiProvider, + }) + ).serialize().deployedIsm; + + // connect to ISM + const opstackIsm = OPStackIsm__factory.connect( + opStackIsmAddress, + this.multiProvider.getSignerOrProvider(config.destinationChain), + ); + + // deploy opstack hook + const hook = await this.deployer.deployContract({ + chain, + contractKey: HookType.OP_STACK, + constructorArgs: [ + mailbox, + this.multiProvider.getDomainId(config.destinationChain), + addressToBytes32(opstackIsm.address), + config.nativeBridge, + ], + }); + + // set authorized hook on opstack ism + const authorizedHook = await opstackIsm.authorizedHook(); + if (authorizedHook === addressToBytes32(hook.address)) { + this.logger.debug( + 'Authorized hook already set on ism %s', + opstackIsm.address, + ); + return hook; + } else if ( + authorizedHook !== addressToBytes32(ethers.constants.AddressZero) + ) { + this.logger.debug( + 'Authorized hook mismatch on ism %s, expected %s, got %s', + opstackIsm.address, + addressToBytes32(hook.address), + authorizedHook, + ); + throw new Error('Authorized hook mismatch'); + } + + // check if mismatch and redeploy hook + this.logger.debug( + 'Setting authorized hook %s on ism % on destination %s', + hook.address, + opstackIsm.address, + config.destinationChain, + ); + await this.multiProvider.handleTx( + config.destinationChain, + opstackIsm.setAuthorizedHook( + addressToBytes32(hook.address), + this.multiProvider.getTransactionOverrides(config.destinationChain), + ), + ); + + return hook; + } + + protected async deployRoutingHook({ + config, + }: { + config: DomainRoutingHookConfig | FallbackRoutingHookConfig; + }): Promise { + // originally set owner to deployer so we can set hooks + const deployerAddress = await this.multiProvider.getSignerAddress( + this.chain, + ); + + let routingHook: DomainRoutingHook | FallbackDomainRoutingHook; + if (config.type === HookType.FALLBACK_ROUTING) { + // deploy fallback hook + const fallbackHook = await this.deploy({ config: config.fallback }); + // deploy routing hook with fallback + routingHook = await this.deployer.deployContract({ + chain: this.chain, + contractKey: HookType.FALLBACK_ROUTING, + constructorArgs: [ + this.args.addresses.mailbox, + deployerAddress, + fallbackHook.address, + ], + }); + } else { + // deploy routing hook + routingHook = await this.deployer.deployContract({ + chain: this.chain, + contractKey: HookType.ROUTING, + constructorArgs: [this.args.addresses.mailbox, deployerAddress], + }); + } + + // compute the hooks that need to be set + const hooksToSet = await this.computeRoutingHooksToSet({ + currentDomains: {}, + targetDomains: config.domains, + }); + + // set hooks + await this.multiProvider.handleTx( + this.chain, + routingHook.setHooks(hooksToSet, this.txOverrides), + ); + + // transfer ownership + await this.multiProvider.handleTx( + this.chain, + routingHook.transferOwnership(config.owner, this.txOverrides), + ); + + // return a fully configured routing hook + return routingHook; + } + + protected async deployIgpHook({ + config, + }: { + config: IgpHookConfig; + }): Promise { + this.logger.debug('Deploying IGP as hook...'); + + // Deploy the StorageGasOracle + const storageGasOracle = await this.deployStorageGasOracle({ + config, + }); + + // Deploy the InterchainGasPaymaster + const interchainGasPaymaster = await this.deployInterchainGasPaymaster({ + storageGasOracle, + config, + }); + + return interchainGasPaymaster; + } + + protected async deployInterchainGasPaymaster({ + storageGasOracle, + config, + }: { + storageGasOracle: StorageGasOracle; + config: IgpConfig; + }): Promise { + const deployerAddress = await this.multiProvider.getSignerAddress( + this.chain, + ); + + const igp = await this.deployer.deployProxiedContract({ + chain: this.chain, + contractKey: HookType.INTERCHAIN_GAS_PAYMASTER, + contractName: HookType.INTERCHAIN_GAS_PAYMASTER, + proxyAdmin: this.args.addresses.proxyAdmin, + constructorArgs: [], + initializeArgs: [deployerAddress, config.beneficiary], + }); + + const gasParamsToSet: InterchainGasPaymaster.GasParamStruct[] = []; + for (const [remote, gasOverhead] of Object.entries(config.overhead)) { + // Note: non-EVM remotes actually *are* supported, provided that the remote domain is in the MultiProvider. + // Previously would check core metadata for non EVMs and fallback to multiprovider for custom EVMs + const remoteDomain = this.multiProvider.tryGetDomainId(remote); + if (!remoteDomain) { + this.logger.warn( + `Skipping overhead ${this.chain} -> ${remote}. Expected if the remote is a non-EVM chain.`, + ); + continue; + } + + this.logger.debug( + `Setting gas params for ${this.chain} -> ${remote}: gasOverhead = ${gasOverhead} gasOracle = ${storageGasOracle.address}`, + ); + gasParamsToSet.push({ + remoteDomain, + config: { + gasOverhead, + gasOracle: storageGasOracle.address, + }, + }); + } + + if (gasParamsToSet.length > 0) { + await this.multiProvider.handleTx( + this.chain, + igp.setDestinationGasConfigs(gasParamsToSet, this.txOverrides), + ); + } + + // Transfer igp to the configured owner + await this.multiProvider.handleTx( + this.chain, + igp.transferOwnership(config.owner, this.txOverrides), + ); + + return igp; + } + + protected async deployStorageGasOracle({ + config, + }: { + config: IgpConfig; + }): Promise { + const gasOracle = await this.deployer.deployContract({ + chain: this.chain, + contractKey: 'storageGasOracle', + constructorArgs: [], + }); + + if (!config.oracleConfig) { + this.logger.debug('No oracle config provided, skipping...'); + return gasOracle; + } + + this.logger.info(`Configuring gas oracle from ${this.chain}...`); + const configsToSet: Array = []; + + for (const [remote, desired] of Object.entries(config.oracleConfig)) { + // Note: non-EVM remotes actually *are* supported, provided that the remote domain is in the MultiProvider. + // Previously would check core metadata for non EVMs and fallback to multiprovider for custom EVMs + const remoteDomain = this.multiProvider.tryGetDomainId(remote); + if (!remoteDomain) { + this.logger.warn( + `Skipping gas oracle ${this.chain} -> ${remote}.` + + ' Expected if the remote is a non-EVM chain or the remote domain is not the in the MultiProvider.', + ); + continue; + } + + configsToSet.push({ + remoteDomain, + ...desired, + }); + + // Log an example remote gas cost + const exampleRemoteGas = (config.overhead[remote] ?? 200_000) + 50_000; + const exampleRemoteGasCost = BigNumber.from(desired.tokenExchangeRate) + .mul(desired.gasPrice) + .mul(exampleRemoteGas) + .div(TOKEN_EXCHANGE_RATE_SCALE); + this.logger.info( + `${ + this.chain + } -> ${remote}: ${exampleRemoteGas} remote gas cost: ${ethers.utils.formatEther( + exampleRemoteGasCost, + )}`, + ); + } + + if (configsToSet.length > 0) { + await this.multiProvider.handleTx( + this.chain, + gasOracle.setRemoteGasDataConfigs(configsToSet, this.txOverrides), + ); + } + + // Transfer gas oracle to the configured owner + await this.multiProvider.handleTx( + this.chain, + gasOracle.transferOwnership(config.oracleKey, this.txOverrides), + ); + + return gasOracle; } } diff --git a/typescript/sdk/src/ism/EvmIsmModule.hardhat-test.ts b/typescript/sdk/src/ism/EvmIsmModule.hardhat-test.ts index 488854454..2bb1dd27d 100644 --- a/typescript/sdk/src/ism/EvmIsmModule.hardhat-test.ts +++ b/typescript/sdk/src/ism/EvmIsmModule.hardhat-test.ts @@ -50,43 +50,47 @@ function randomModuleType(): ModuleType { const randomIsmConfig = (depth = 0, maxDepth = 2): IsmConfig => { const moduleType = depth == maxDepth ? ModuleType.MERKLE_ROOT_MULTISIG : randomModuleType(); - if (moduleType === ModuleType.MERKLE_ROOT_MULTISIG) { - const n = randomInt(5, 1); - return randomMultisigIsmConfig(randomInt(n, 1), n); - } else if (moduleType === ModuleType.ROUTING) { - const config: RoutingIsmConfig = { - type: IsmType.ROUTING, - owner: randomAddress(), - domains: Object.fromEntries( - testChains.map((c) => [c, randomIsmConfig(depth + 1)]), - ), - }; - return config; - } else if (moduleType === ModuleType.AGGREGATION) { - const n = randomInt(5, 1); - const modules = new Array(n) - .fill(0) - .map(() => randomIsmConfig(depth + 1)); - const config: AggregationIsmConfig = { - type: IsmType.AGGREGATION, - threshold: randomInt(n, 1), - modules, - }; - return config; - } else if (moduleType === ModuleType.NULL) { - const config: TrustedRelayerIsmConfig = { - type: IsmType.TRUSTED_RELAYER, - relayer: randomAddress(), - }; - return config; - } else { - throw new Error(`Unsupported ISM type: ${moduleType}`); + switch (moduleType) { + case ModuleType.MERKLE_ROOT_MULTISIG: { + const n = randomInt(5, 1); + return randomMultisigIsmConfig(randomInt(n, 1), n); + } + case ModuleType.ROUTING: { + const config: RoutingIsmConfig = { + type: IsmType.ROUTING, + owner: randomAddress(), + domains: Object.fromEntries( + testChains.map((c) => [c, randomIsmConfig(depth + 1)]), + ), + }; + return config; + } + case ModuleType.AGGREGATION: { + const n = randomInt(5, 1); + const modules = new Array(n) + .fill(0) + .map(() => randomIsmConfig(depth + 1)); + const config: AggregationIsmConfig = { + type: IsmType.AGGREGATION, + threshold: randomInt(n, 1), + modules, + }; + return config; + } + case ModuleType.NULL: { + const config: TrustedRelayerIsmConfig = { + type: IsmType.TRUSTED_RELAYER, + relayer: randomAddress(), + }; + return config; + } + default: + throw new Error(`Unsupported ISM type: ${moduleType}`); } }; describe('EvmIsmModule', async () => { let multiProvider: MultiProvider; - let ismFactoryDeployer: HyperlaneProxyFactoryDeployer; let exampleRoutingConfig: RoutingIsmConfig; let mailboxAddress: Address; let newMailboxAddress: Address; @@ -101,10 +105,9 @@ describe('EvmIsmModule', async () => { fundingAccount = funder; multiProvider = MultiProvider.createTestMultiProvider({ signer }); - ismFactoryDeployer = new HyperlaneProxyFactoryDeployer(multiProvider); - const contractsMap = await ismFactoryDeployer.deploy( - multiProvider.mapKnownChains(() => ({})), - ); + const contractsMap = await new HyperlaneProxyFactoryDeployer( + multiProvider, + ).deploy(multiProvider.mapKnownChains(() => ({}))); // get addresses of factories for the chain factoryContracts = contractsMap[chain]; @@ -186,8 +189,7 @@ describe('EvmIsmModule', async () => { const ism = await EvmIsmModule.create({ chain, config, - deployer: ismFactoryDeployer, - factories: factoryAddresses, + proxyFactoryFactories: factoryAddresses, mailbox: mailboxAddress, multiProvider, }); diff --git a/typescript/sdk/src/ism/EvmIsmModule.ts b/typescript/sdk/src/ism/EvmIsmModule.ts index e4c6db641..62e814fc5 100644 --- a/typescript/sdk/src/ism/EvmIsmModule.ts +++ b/typescript/sdk/src/ism/EvmIsmModule.ts @@ -16,8 +16,6 @@ import { OPStackIsm__factory, Ownable__factory, PausableIsm__factory, - StaticAddressSetFactory, - StaticThresholdAddressSetFactory, TestIsm__factory, TrustedRelayerIsm__factory, } from '@hyperlane-xyz/core'; @@ -39,11 +37,12 @@ import { HyperlaneModule, HyperlaneModuleParams, } from '../core/AbstractHyperlaneModule.js'; -import { HyperlaneDeployer } from '../deploy/HyperlaneDeployer.js'; +import { EvmModuleDeployer } from '../deploy/EvmModuleDeployer.js'; import { ProxyFactoryFactories, proxyFactoryFactories, } from '../deploy/contracts.js'; +import { ContractVerifier } from '../deploy/verify/ContractVerifier.js'; import { MultiProvider } from '../providers/MultiProvider.js'; import { AnnotatedEV5Transaction } from '../providers/ProviderType.js'; import { ChainName, ChainNameOrId } from '../types.js'; @@ -73,6 +72,7 @@ export class EvmIsmModule extends HyperlaneModule< > { protected readonly logger = rootLogger.child({ module: 'EvmIsmModule' }); protected readonly reader: EvmIsmReader; + protected readonly deployer: EvmModuleDeployer; protected readonly factories: HyperlaneContracts; // Adding these to reduce how often we need to grab from MultiProvider. @@ -83,18 +83,34 @@ export class EvmIsmModule extends HyperlaneModule< protected constructor( protected readonly multiProvider: MultiProvider, - protected readonly deployer: HyperlaneDeployer, params: HyperlaneModuleParams< IsmConfig, HyperlaneAddresses & IsmModuleAddresses >, + contractVerifier?: ContractVerifier, ) { super(params); this.reader = new EvmIsmReader(multiProvider, params.chain); - const { mailbox: _, deployedIsm: __, ...addresses } = params.addresses; + this.deployer = new EvmModuleDeployer( + this.multiProvider, + {}, + this.logger, + contractVerifier, + ); + this.factories = attachAndConnectContracts( - addresses, + { + staticMerkleRootMultisigIsmFactory: + params.addresses.staticMerkleRootMultisigIsmFactory, + staticMessageIdMultisigIsmFactory: + params.addresses.staticMessageIdMultisigIsmFactory, + staticAggregationIsmFactory: + params.addresses.staticAggregationIsmFactory, + staticAggregationHookFactory: + params.addresses.staticAggregationHookFactory, + domainRoutingIsmFactory: params.addresses.domainRoutingIsmFactory, + }, proxyFactoryFactories, multiProvider.getSigner(params.chain), ); @@ -140,9 +156,9 @@ export class EvmIsmModule extends HyperlaneModule< // Else, we have to figure out what an update for this ISM entails - // If target config is a custom ISM, just update the address - // if config -> custom ISM, update address - // if custom ISM -> custom ISM, update address + // If target config is an address ISM, just update the address + // if config -> address ISM, update address + // if address ISM -> address ISM, update address if (typeof targetConfig === 'string') { // TODO: https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/3773 this.args.addresses.deployedIsm = targetConfig; @@ -151,7 +167,7 @@ export class EvmIsmModule extends HyperlaneModule< // Check if we need to deploy a new ISM if ( - // if custom ISM -> config, do a new deploy + // if address ISM -> config, do a new deploy typeof currentConfig === 'string' || // if config -> config, AND types are different, do a new deploy currentConfig.type !== targetConfig.type || @@ -242,21 +258,23 @@ export class EvmIsmModule extends HyperlaneModule< } // manually write static create function - public static async create(params: { + public static async create({ + chain, + config, + proxyFactoryFactories, + mailbox, + multiProvider, + }: { chain: ChainNameOrId; config: IsmConfig; - deployer: HyperlaneDeployer; - factories: HyperlaneAddresses; + proxyFactoryFactories: HyperlaneAddresses; mailbox: Address; multiProvider: MultiProvider; }): Promise { - const { chain, config, deployer, factories, mailbox, multiProvider } = - params; - // instantiate new EvmIsmModule - const module = new EvmIsmModule(multiProvider, deployer, { + const module = new EvmIsmModule(multiProvider, { addresses: { - ...factories, + ...proxyFactoryFactories, mailbox, deployedIsm: ethers.constants.AddressZero, }, @@ -330,10 +348,10 @@ export class EvmIsmModule extends HyperlaneModule< }: { config: C; }): Promise { - // If it's a custom ISM, just return a base ISM + // If it's an address ISM, just return a base ISM if (typeof config === 'string') { // TODO: https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/3773 - // we can remove the ts-ignore once we have a proper type for custom ISMs + // we can remove the ts-ignore once we have a proper type for address ISMs // @ts-ignore return IInterchainSecurityModule__factory.connect( config, @@ -368,40 +386,40 @@ export class EvmIsmModule extends HyperlaneModule< }); case IsmType.OP_STACK: - return this.deployer.deployContractFromFactory( - this.chain, - new OPStackIsm__factory(), - IsmType.OP_STACK, - [config.nativeBridge], - ); + return this.deployer.deployContractFromFactory({ + chain: this.chain, + factory: new OPStackIsm__factory(), + contractName: IsmType.OP_STACK, + constructorArgs: [config.nativeBridge], + }); case IsmType.PAUSABLE: - return this.deployer.deployContractFromFactory( - this.chain, - new PausableIsm__factory(), - IsmType.PAUSABLE, - [config.owner], - ); + return this.deployer.deployContractFromFactory({ + chain: this.chain, + factory: new PausableIsm__factory(), + contractName: IsmType.PAUSABLE, + constructorArgs: [config.owner], + }); case IsmType.TRUSTED_RELAYER: assert( this.args.addresses.mailbox, `Mailbox address is required for deploying ${ismType}`, ); - return this.deployer.deployContractFromFactory( - this.chain, - new TrustedRelayerIsm__factory(), - IsmType.TRUSTED_RELAYER, - [this.args.addresses.mailbox, config.relayer], - ); + return this.deployer.deployContractFromFactory({ + chain: this.chain, + factory: new TrustedRelayerIsm__factory(), + contractName: IsmType.TRUSTED_RELAYER, + constructorArgs: [this.args.addresses.mailbox, config.relayer], + }); case IsmType.TEST_ISM: - return this.deployer.deployContractFromFactory( - this.chain, - new TestIsm__factory(), - IsmType.TEST_ISM, - [], - ); + return this.deployer.deployContractFromFactory({ + chain: this.chain, + factory: new TestIsm__factory(), + contractName: IsmType.TEST_ISM, + constructorArgs: [], + }); default: throw new Error(`Unsupported ISM type ${ismType}`); @@ -421,7 +439,7 @@ export class EvmIsmModule extends HyperlaneModule< ? 'staticMerkleRootMultisigIsmFactory' : 'staticMessageIdMultisigIsmFactory'; - const address = await EvmIsmModule.deployStaticAddressSet({ + const address = await EvmModuleDeployer.deployStaticAddressSet({ chain: this.chain, factory: this.factories[factoryName], values: config.validators, @@ -545,7 +563,7 @@ export class EvmIsmModule extends HyperlaneModule< } const factoryName = 'staticAggregationIsmFactory'; - const address = await EvmIsmModule.deployStaticAddressSet({ + const address = await EvmModuleDeployer.deployStaticAddressSet({ chain: this.chain, factory: this.factories[factoryName], values: addresses, @@ -578,61 +596,6 @@ export class EvmIsmModule extends HyperlaneModule< this.args.addresses.mailbox = newMailboxAddress; } - // Public so it can be reused by the hook module. - // Caller of this function is responsible for verifying the contract - // because they know exactly which factory is being called. - public static async deployStaticAddressSet({ - chain, - factory, - values, - logger, - threshold = values.length, - multiProvider, - }: { - chain: ChainName; - factory: StaticThresholdAddressSetFactory | StaticAddressSetFactory; - values: Address[]; - logger: Logger; - threshold?: number; - multiProvider: MultiProvider; - }): Promise
{ - const address = await factory['getAddress(address[],uint8)']( - values, - threshold, - ); - const code = await multiProvider.getProvider(chain).getCode(address); - if (code === '0x') { - logger.debug( - `Deploying new ${threshold} of ${values.length} address set to ${chain}`, - ); - const overrides = multiProvider.getTransactionOverrides(chain); - const hash = await factory['deploy(address[],uint8)']( - values, - threshold, - overrides, - ); - await multiProvider.handleTx(chain, hash); - } else { - logger.debug( - `Recovered ${threshold} of ${values.length} address set on ${chain}: ${address}`, - ); - } - - // TODO: figure out how to get the constructor arguments for manual deploy TXs - // const verificationInput = buildVerificationInput( - // NAME, - // ADDRESS, - // CONSTRUCTOR_ARGS, - // ); - // await this.deployer.verifyContract( - // this.chainName, - // verificationInput, - // logger, - // ); - - return address; - } - // filtering out domains which are not part of the multiprovider private filterRoutingIsmDomains({ config }: { config: RoutingIsmConfig }) { const availableDomainIds: number[] = []; diff --git a/typescript/utils/src/objects.ts b/typescript/utils/src/objects.ts index 531997f1e..4ca6221c0 100644 --- a/typescript/utils/src/objects.ts +++ b/typescript/utils/src/objects.ts @@ -2,6 +2,7 @@ import { deepStrictEqual } from 'node:assert/strict'; import { stringify as yamlStringify } from 'yaml'; import { ethersBigNumberSerializer, rootLogger } from './logging.js'; +import { WithAddress } from './types.js'; import { assert } from './validation.js'; export function isObject(item: any) { @@ -143,7 +144,7 @@ export function arrayToObject(keys: Array, val = true) { } export function stringifyObject( - object: object, + object: any, format: 'json' | 'yaml' = 'yaml', space?: number, ): string { @@ -157,7 +158,7 @@ export function stringifyObject( } // Function to recursively remove 'address' properties and lowercase string properties -export function normalizeConfig(obj: any): any { +export function normalizeConfig(obj: WithAddress): any { if (Array.isArray(obj)) { return obj.map(normalizeConfig); } else if (obj !== null && typeof obj === 'object') { From bf7ad09da326a45555199a94032b52eba34bab20 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Wed, 19 Jun 2024 15:31:57 -0400 Subject: [PATCH 45/73] feat(cli): add `warp --symbol` flag (#3992) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Description - This allows users to interact read warp routes from the registry with symbol identifier rather than chain/address ### Drive-by Rename `wei` to `amount` ### Backward compatibility No, outputs chain map ### Testing - Manual (single) ```sh $ yarn hyperlane warp read --symbol EZETH $ yarn hyperlane warp send --symbol EZETH ``` (multiple) ```sh $ yarn hyperlane warp read --symbol USDC $ yarn hyperlane warp send --symbol USDC ``` --------- Co-authored-by: Noah Bayindirli 🥂 --- .changeset/mean-books-clean.md | 7 ++ typescript/cli/package.json | 2 +- typescript/cli/src/commands/options.ts | 7 +- typescript/cli/src/commands/warp.ts | 106 +++++++++++++++++++------ typescript/cli/src/config/warp.ts | 2 +- typescript/cli/src/send/transfer.ts | 21 +++-- typescript/cli/src/utils/tokens.ts | 34 +++++++- typescript/helloworld/package.json | 2 +- typescript/infra/package.json | 2 +- yarn.lock | 14 ++-- 10 files changed, 145 insertions(+), 52 deletions(-) create mode 100644 .changeset/mean-books-clean.md diff --git a/.changeset/mean-books-clean.md b/.changeset/mean-books-clean.md new file mode 100644 index 000000000..e41ee504d --- /dev/null +++ b/.changeset/mean-books-clean.md @@ -0,0 +1,7 @@ +--- +"@hyperlane-xyz/cli": minor +"@hyperlane-xyz/helloworld": minor +"@hyperlane-xyz/infra": minor +--- + +feat(cli): add `warp --symbol` flag diff --git a/typescript/cli/package.json b/typescript/cli/package.json index 2c3cb62c4..f08b4ce68 100644 --- a/typescript/cli/package.json +++ b/typescript/cli/package.json @@ -5,7 +5,7 @@ "dependencies": { "@aws-sdk/client-kms": "^3.577.0", "@aws-sdk/client-s3": "^3.577.0", - "@hyperlane-xyz/registry": "1.3.0", + "@hyperlane-xyz/registry": "^2.1.0", "@hyperlane-xyz/sdk": "3.15.0", "@hyperlane-xyz/utils": "3.15.0", "@inquirer/prompts": "^3.0.0", diff --git a/typescript/cli/src/commands/options.ts b/typescript/cli/src/commands/options.ts index 2ef4eb8e1..0d14b91e7 100644 --- a/typescript/cli/src/commands/options.ts +++ b/typescript/cli/src/commands/options.ts @@ -96,8 +96,6 @@ export const warpCoreConfigCommandOption: Options = { type: 'string', description: 'File path to Warp Route config', alias: 'w', - // TODO make this optional and have the commands get it from the registry - demandOption: true, }; export const agentConfigCommandOption = ( @@ -155,6 +153,11 @@ export const chainCommandOption: Options = { description: 'The specific chain to perform operations with.', }; +export const symbolCommandOption: Options = { + type: 'string', + description: 'Token symbol (e.g. ETH, USDC)', +}; + export const validatorCommandOption: Options = { type: 'string', description: 'Comma separated list of validator addresses', diff --git a/typescript/cli/src/commands/warp.ts b/typescript/cli/src/commands/warp.ts index 536ef0b1e..1b61c7aff 100644 --- a/typescript/cli/src/commands/warp.ts +++ b/typescript/cli/src/commands/warp.ts @@ -1,18 +1,27 @@ import { stringify as yamlStringify } from 'yaml'; import { CommandModule } from 'yargs'; -import { EvmERC20WarpRouteReader } from '@hyperlane-xyz/sdk'; +import { + ChainMap, + EvmERC20WarpRouteReader, + WarpCoreConfig, +} from '@hyperlane-xyz/sdk'; +import { objMap, promiseObjAll } from '@hyperlane-xyz/utils'; -import { createWarpRouteDeployConfig } from '../config/warp.js'; +import { + createWarpRouteDeployConfig, + readWarpCoreConfig, +} from '../config/warp.js'; import { CommandModuleWithContext, CommandModuleWithWriteContext, } from '../context/types.js'; import { evaluateIfDryRunFailure } from '../deploy/dry-run.js'; import { runWarpRouteDeploy } from '../deploy/warp.js'; -import { log, logGray, logGreen } from '../logger.js'; +import { log, logGray, logGreen, logRed } from '../logger.js'; import { sendTestTransfer } from '../send/transfer.js'; import { indentYamlOrJson, writeYamlOrJson } from '../utils/files.js'; +import { selectRegistryWarpRoute } from '../utils/tokens.js'; import { addressCommandOption, @@ -20,6 +29,7 @@ import { dryRunCommandOption, fromAddressCommandOption, outputFileCommandOption, + symbolCommandOption, warpCoreConfigCommandOption, warpDeploymentConfigCommandOption, } from './options.js'; @@ -100,51 +110,77 @@ export const configure: CommandModuleWithContext<{ }; export const read: CommandModuleWithContext<{ - chain: string; - address: string; - out: string; + chain?: string; + address?: string; + out?: string; + symbol?: string; }> = { command: 'read', - describe: 'Reads the warp route config at the given path.', + describe: 'Derive the warp route config from onchain artifacts', builder: { + symbol: { + ...symbolCommandOption, + demandOption: false, + }, chain: { ...chainCommandOption, - demandOption: true, + demandOption: false, }, address: addressCommandOption( 'Address of the router contract to read.', - true, + false, ), out: outputFileCommandOption(), }, - handler: async ({ context, chain, address, out }) => { + handler: async ({ context, chain, address, out, symbol }) => { logGray('Hyperlane Warp Reader'); logGray('---------------------'); const { multiProvider } = context; - const evmERC20WarpRouteReader = new EvmERC20WarpRouteReader( - multiProvider, - chain, - ); - const warpRouteConfig = await evmERC20WarpRouteReader.deriveWarpRouteConfig( - address, + + let addresses: ChainMap; + if (symbol) { + const warpCoreConfig = await selectRegistryWarpRoute( + context.registry, + symbol, + ); + addresses = Object.fromEntries( + warpCoreConfig.tokens.map((t) => [t.chainName, t.addressOrDenom!]), + ); + } else if (chain && address) { + addresses = { + [chain]: address, + }; + } else { + logGreen(`Please specify either a symbol or chain and address`); + process.exit(0); + } + + const config = await promiseObjAll( + objMap(addresses, async (chain, address) => + new EvmERC20WarpRouteReader(multiProvider, chain).deriveWarpRouteConfig( + address, + ), + ), ); + if (out) { - writeYamlOrJson(out, warpRouteConfig, 'yaml'); + writeYamlOrJson(out, config, 'yaml'); logGreen(`✅ Warp route config written successfully to ${out}:\n`); } else { logGreen(`✅ Warp route config read successfully:\n`); } - log(indentYamlOrJson(yamlStringify(warpRouteConfig, null, 2), 4)); + log(indentYamlOrJson(yamlStringify(config, null, 2), 4)); process.exit(0); }, }; const send: CommandModuleWithWriteContext< MessageOptionsArgTypes & { - warp: string; + warp?: string; + symbol?: string; router?: string; - wei: string; + amount: string; recipient?: string; } > = { @@ -152,10 +188,17 @@ const send: CommandModuleWithWriteContext< describe: 'Send a test token transfer on a warp route', builder: { ...messageOptions, - warp: warpCoreConfigCommandOption, - wei: { + symbol: { + ...symbolCommandOption, + demandOption: false, + }, + warp: { + ...warpCoreConfigCommandOption, + demandOption: false, + }, + amount: { type: 'string', - description: 'Amount in wei to send', + description: 'Amount to send (in smallest unit)', default: 1, }, recipient: { @@ -170,16 +213,27 @@ const send: CommandModuleWithWriteContext< timeout, quick, relay, + symbol, warp, - wei, + amount, recipient, }) => { + let warpCoreConfig: WarpCoreConfig; + if (symbol) { + warpCoreConfig = await selectRegistryWarpRoute(context.registry, symbol); + } else if (warp) { + warpCoreConfig = readWarpCoreConfig(warp); + } else { + logRed(`Please specify either a symbol or warp config`); + process.exit(0); + } + await sendTestTransfer({ context, - warpConfigPath: warp, + warpCoreConfig, origin, destination, - wei, + amount, recipient, timeoutSec: timeout, skipWaitForDelivery: quick, diff --git a/typescript/cli/src/config/warp.ts b/typescript/cli/src/config/warp.ts index 826622155..759bf7c40 100644 --- a/typescript/cli/src/config/warp.ts +++ b/typescript/cli/src/config/warp.ts @@ -195,7 +195,7 @@ export async function createWarpRouteDeployConfig({ // Note, this is different than the function above which reads a config // for a DEPLOYMENT. This gets a config for using a warp route (aka WarpCoreConfig) -export function readWarpRouteConfig(filePath: string): WarpCoreConfig { +export function readWarpCoreConfig(filePath: string): WarpCoreConfig { const config = readYamlOrJson(filePath); if (!config) throw new Error(`No warp route config found at ${filePath}`); return WarpCoreConfigSchema.parse(config); diff --git a/typescript/cli/src/send/transfer.ts b/typescript/cli/src/send/transfer.ts index 126ddf426..2df762b88 100644 --- a/typescript/cli/src/send/transfer.ts +++ b/typescript/cli/src/send/transfer.ts @@ -10,7 +10,6 @@ import { } from '@hyperlane-xyz/sdk'; import { timeout } from '@hyperlane-xyz/utils'; -import { readWarpRouteConfig } from '../config/warp.js'; import { MINIMUM_TEST_SEND_GAS } from '../consts.js'; import { WriteCommandContext } from '../context/types.js'; import { runPreflightChecksForChains } from '../deploy/utils.js'; @@ -20,20 +19,20 @@ import { runTokenSelectionStep } from '../utils/tokens.js'; export async function sendTestTransfer({ context, - warpConfigPath, + warpCoreConfig, origin, destination, - wei, + amount, recipient, timeoutSec, skipWaitForDelivery, selfRelay, }: { context: WriteCommandContext; - warpConfigPath: string; + warpCoreConfig: WarpCoreConfig; origin?: ChainName; destination?: ChainName; - wei: string; + amount: string; recipient?: string; timeoutSec: number; skipWaitForDelivery: boolean; @@ -41,8 +40,6 @@ export async function sendTestTransfer({ }) { const { chainMetadata } = context; - const warpCoreConfig = readWarpRouteConfig(warpConfigPath); - if (!origin) { origin = await runSingleChainSelectionStep( chainMetadata, @@ -70,7 +67,7 @@ export async function sendTestTransfer({ origin, destination, warpCoreConfig, - wei, + amount, recipient, skipWaitForDelivery, selfRelay, @@ -85,7 +82,7 @@ async function executeDelivery({ origin, destination, warpCoreConfig, - wei, + amount, recipient, skipWaitForDelivery, selfRelay, @@ -94,7 +91,7 @@ async function executeDelivery({ origin: ChainName; destination: ChainName; warpCoreConfig: WarpCoreConfig; - wei: string; + amount: string; recipient?: string; skipWaitForDelivery: boolean; selfRelay?: boolean; @@ -131,7 +128,7 @@ async function executeDelivery({ const senderAddress = await signer.getAddress(); const errors = await warpCore.validateTransfer({ - originTokenAmount: token.amount(wei), + originTokenAmount: token.amount(amount), destination, recipient: recipient ?? senderAddress, sender: senderAddress, @@ -142,7 +139,7 @@ async function executeDelivery({ } const transferTxs = await warpCore.getTransferRemoteTxs({ - originTokenAmount: new TokenAmount(wei, token), + originTokenAmount: new TokenAmount(amount, token), destination, sender: senderAddress, recipient: recipient ?? senderAddress, diff --git a/typescript/cli/src/utils/tokens.ts b/typescript/cli/src/utils/tokens.ts index ed876238c..b31a2ddf3 100644 --- a/typescript/cli/src/utils/tokens.ts +++ b/typescript/cli/src/utils/tokens.ts @@ -1,6 +1,9 @@ import select from '@inquirer/select'; -import { Token } from '@hyperlane-xyz/sdk'; +import { IRegistry } from '@hyperlane-xyz/registry'; +import { Token, WarpCoreConfig } from '@hyperlane-xyz/sdk'; + +import { logGreen, logRed } from '../logger.js'; export async function runTokenSelectionStep( tokens: Token[], @@ -17,3 +20,32 @@ export async function runTokenSelectionStep( })) as string; return routerAddress; } + +export async function selectRegistryWarpRoute( + registry: IRegistry, + symbol: string, +): Promise { + const matching = await registry.getWarpRoutes({ + symbol, + }); + const routes = Object.entries(matching); + + let warpCoreConfig: WarpCoreConfig; + if (routes.length === 0) { + logRed(`No warp routes found for symbol ${symbol}`); + process.exit(0); + } else if (routes.length === 1) { + warpCoreConfig = routes[0][1]; + } else { + logGreen(`Multiple warp routes found for symbol ${symbol}`); + const chosenRouteId = await select({ + message: 'Select from matching warp routes', + choices: routes.map(([routeId, _]) => ({ + value: routeId, + })), + }); + warpCoreConfig = matching[chosenRouteId]; + } + + return warpCoreConfig; +} diff --git a/typescript/helloworld/package.json b/typescript/helloworld/package.json index 22138b9bc..b9f9ff06c 100644 --- a/typescript/helloworld/package.json +++ b/typescript/helloworld/package.json @@ -4,7 +4,7 @@ "version": "3.15.0", "dependencies": { "@hyperlane-xyz/core": "3.15.0", - "@hyperlane-xyz/registry": "1.3.0", + "@hyperlane-xyz/registry": "^2.1.0", "@hyperlane-xyz/sdk": "3.15.0", "@openzeppelin/contracts-upgradeable": "^4.9.3", "ethers": "^5.7.2" diff --git a/typescript/infra/package.json b/typescript/infra/package.json index daede08e4..6bf392c51 100644 --- a/typescript/infra/package.json +++ b/typescript/infra/package.json @@ -14,7 +14,7 @@ "@ethersproject/providers": "^5.7.2", "@google-cloud/secret-manager": "^5.5.0", "@hyperlane-xyz/helloworld": "3.15.0", - "@hyperlane-xyz/registry": "1.3.0", + "@hyperlane-xyz/registry": "^2.1.0", "@hyperlane-xyz/sdk": "3.15.0", "@hyperlane-xyz/utils": "3.15.0", "@nomiclabs/hardhat-etherscan": "^3.0.3", diff --git a/yarn.lock b/yarn.lock index 70970a80f..fd561c5ec 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5683,7 +5683,7 @@ __metadata: "@aws-sdk/client-s3": "npm:^3.577.0" "@ethersproject/abi": "npm:*" "@ethersproject/providers": "npm:*" - "@hyperlane-xyz/registry": "npm:1.3.0" + "@hyperlane-xyz/registry": "npm:^2.1.0" "@hyperlane-xyz/sdk": "npm:3.15.0" "@hyperlane-xyz/utils": "npm:3.15.0" "@inquirer/prompts": "npm:^3.0.0" @@ -5775,7 +5775,7 @@ __metadata: resolution: "@hyperlane-xyz/helloworld@workspace:typescript/helloworld" dependencies: "@hyperlane-xyz/core": "npm:3.15.0" - "@hyperlane-xyz/registry": "npm:1.3.0" + "@hyperlane-xyz/registry": "npm:^2.1.0" "@hyperlane-xyz/sdk": "npm:3.15.0" "@nomiclabs/hardhat-ethers": "npm:^2.2.3" "@nomiclabs/hardhat-waffle": "npm:^2.0.6" @@ -5824,7 +5824,7 @@ __metadata: "@ethersproject/providers": "npm:^5.7.2" "@google-cloud/secret-manager": "npm:^5.5.0" "@hyperlane-xyz/helloworld": "npm:3.15.0" - "@hyperlane-xyz/registry": "npm:1.3.0" + "@hyperlane-xyz/registry": "npm:^2.1.0" "@hyperlane-xyz/sdk": "npm:3.15.0" "@hyperlane-xyz/utils": "npm:3.15.0" "@nomiclabs/hardhat-ethers": "npm:^2.2.3" @@ -5876,13 +5876,13 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/registry@npm:1.3.0": - version: 1.3.0 - resolution: "@hyperlane-xyz/registry@npm:1.3.0" +"@hyperlane-xyz/registry@npm:^2.1.0": + version: 2.1.0 + resolution: "@hyperlane-xyz/registry@npm:2.1.0" dependencies: yaml: "npm:^2" zod: "npm:^3.21.2" - checksum: 2cbdfd9e8958d0babde7104dfb0c98def7edb5f87f5f4679b09467a6a9b531884f187fcbc16fd85b00e304ef8fa3beb0a0779555b2c3edc1936541a0e878a73d + checksum: cfcd441dcbb4886a4ecd90dffaeb7a0fd81c0a126423b23cf100cd554470fe88ceb35b41271d779f0390c42293fd2c799742eeff6dd42ca42f3c799a8e88b96b languageName: node linkType: hard From b7003cf3561c0d81da3f83d0faa9bc7a1766739a Mon Sep 17 00:00:00 2001 From: Lee <6251863+ltyu@users.noreply.github.com> Date: Wed, 19 Jun 2024 16:22:23 -0400 Subject: [PATCH 46/73] feat: add stdout.rows to pagesize calculation with DEFAULT_PAGE_SIZE (#3968) ### Description - Add process.stdout.rows to pageSize adjustment for chain selection - Defaults to 15 if process.stdout.rows < 0 ### Backward compatibility Yes ### Testing Manual --- .changeset/wise-cobras-juggle.md | 5 +++++ typescript/cli/src/utils/chains.ts | 6 ++++-- typescript/cli/src/utils/cli-options.ts | 17 +++++++++++++++++ 3 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 .changeset/wise-cobras-juggle.md create mode 100644 typescript/cli/src/utils/cli-options.ts diff --git a/.changeset/wise-cobras-juggle.md b/.changeset/wise-cobras-juggle.md new file mode 100644 index 000000000..4c7bb1a4a --- /dev/null +++ b/.changeset/wise-cobras-juggle.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/cli': minor +--- + +Add stdout.rows to pagesize calculation with DEFAULT_PAGE_SIZE diff --git a/typescript/cli/src/utils/chains.ts b/typescript/cli/src/utils/chains.ts index dc010b7c7..ed147cc69 100644 --- a/typescript/cli/src/utils/chains.ts +++ b/typescript/cli/src/utils/chains.ts @@ -6,6 +6,8 @@ import { ChainMap, ChainMetadata } from '@hyperlane-xyz/sdk'; import { log, logRed, logTip } from '../logger.js'; +import { calculatePageSize } from './cli-options.js'; + // A special value marker to indicate user selected // a new chain in the list const NEW_CHAIN_MARKER = '__new__'; @@ -18,7 +20,7 @@ export async function runSingleChainSelectionStep( const chain = (await select({ message, choices, - pageSize: 30, + pageSize: calculatePageSize(2), })) as string; handleNewChain([chain]); return chain; @@ -35,7 +37,7 @@ export async function runMultiChainSelectionStep( const chains = (await checkbox({ message, choices, - pageSize: 30, + pageSize: calculatePageSize(2), })) as string[]; handleNewChain(chains); if (requireMultiple && chains?.length < 2) { diff --git a/typescript/cli/src/utils/cli-options.ts b/typescript/cli/src/utils/cli-options.ts new file mode 100644 index 000000000..415452e7e --- /dev/null +++ b/typescript/cli/src/utils/cli-options.ts @@ -0,0 +1,17 @@ +// Functions used to manipulate CLI specific options + +/** + * Calculates the page size for a CLI Terminal output, taking into account the number of lines to skip and a default page size. + * + * @param skipSize - The number of lines to skip, which can be used to skip previous prompts. + * @param defaultPageSize - The default page size to use if the terminal height is too small. + * @returns The calculated pageSize, which is the terminal height minus the skip size, or the default page size if the terminal height is too small. + */ +export function calculatePageSize( + skipSize: number = 0, + defaultPageSize: number = 15, +) { + return process.stdout.rows > skipSize + ? process.stdout.rows - skipSize + : defaultPageSize; +} From 290c8cb6f6297589f293ac9eead2105077e78d29 Mon Sep 17 00:00:00 2001 From: Nam Chu Hoai Date: Wed, 19 Jun 2024 16:34:46 -0400 Subject: [PATCH 47/73] feat: Script to generate Renzo Warp Route Configuration (#3983) ### Description ### Drive-by changes ### Related issues ### Backward compatibility ### Testing --- typescript/infra/package.json | 1 + .../generate-renzo-warp-route-config.ts | 150 ++++++++++++++++++ typescript/sdk/src/ism/multisig.ts | 4 +- yarn.lock | 10 ++ 4 files changed, 163 insertions(+), 2 deletions(-) create mode 100644 typescript/infra/scripts/generate-renzo-warp-route-config.ts diff --git a/typescript/infra/package.json b/typescript/infra/package.json index 6bf392c51..e45e312e2 100644 --- a/typescript/infra/package.json +++ b/typescript/infra/package.json @@ -25,6 +25,7 @@ "json-stable-stringify": "^1.1.1", "prom-client": "^14.0.1", "prompts": "^2.4.2", + "yaml": "^2.4.5", "yargs": "^17.7.2" }, "devDependencies": { diff --git a/typescript/infra/scripts/generate-renzo-warp-route-config.ts b/typescript/infra/scripts/generate-renzo-warp-route-config.ts new file mode 100644 index 000000000..e41b7c32e --- /dev/null +++ b/typescript/infra/scripts/generate-renzo-warp-route-config.ts @@ -0,0 +1,150 @@ +import { writeFileSync } from 'fs'; +import { stringify as yamlStringify } from 'yaml'; + +import { GithubRegistry } from '@hyperlane-xyz/registry'; +import { + IsmType, + TokenRouterConfig, + TokenType, + WarpRouteDeployConfig, + WarpRouteDeployConfigSchema, + buildAggregationIsmConfigs, +} from '@hyperlane-xyz/sdk'; + +const lockbox = '0xC8140dA31E6bCa19b287cC35531c2212763C2059'; +const xERC20 = '0x2416092f143378750bb29b79eD961ab195CcEea5'; +const lockboxChain = 'ethereum'; + +const chainsToDeploy = [ + 'arbitrum', + 'optimism', + 'base', + 'blast', + 'bsc', + 'mode', + 'linea', + 'ethereum', +]; + +const ezEthValidators = { + arbitrum: { + threshold: 1, + validators: [ + '0xc27032c6bbd48c20005f552af3aaa0dbf14260f3', // Renzo + '0x9bCcFAd3BD12Ef0Ee8aE839dD9ED7835BcCaDc9D', // Everclear + ], + }, + optimism: { + threshold: 1, + validators: [ + '0xe2593D205F5E7F74A50fA900824501084E092eBd', // Renzo + '0x6f4cb8e96db5d44422a4495faa73fffb9d30e9e2', // Everclear + ], + }, + base: { + threshold: 1, + validators: [ + '0x25BA4eE5268CbfB8D69BAc531Aa10368778702BD', // Renzo + '0x9ec803b503e9c7d2611e231521ef3fde73f7a21c', // Everclear + ], + }, + blast: { + threshold: 1, + validators: [ + '0x54Bb0036F777202371429e062FE6AEE0d59442F9', // Renzo + '0x1652d8ba766821cf01aeea34306dfc1cab964a32', // Everclear + ], + }, + bsc: { + threshold: 1, + validators: [ + '0x3156Db97a3B3e2dcc3D69FdDfD3e12dc7c937b6D', // Renzo + '0x9a0326c43e4713ae2477f09e0f28ffedc24d8266', // Everclear + ], + }, + mode: { + threshold: 1, + validators: [ + '0x7e29608C6E5792bBf9128599ca309Be0728af7B4', // Renzo + '0x456fbbe05484fc9f2f38ea09648424f54d6872be', // Everclear + ], + }, + linea: { + threshold: 1, + validators: [ + '0xcb3e44EdD2229860bDBaA58Ba2c3817D111bEE9A', // Renzo + '0x06a5a2a429560034d38bf62ca6d470942535947e', // Everclear + ], + }, + ethereum: { + threshold: 1, + validators: [ + '0xc7f7b94a6BaF2FFFa54DfE1dDE6E5Fcbb749e04f', // Renzo + '0x1fd889337F60986aa57166bc5AC121eFD13e4fdd', // Everclear + ], + }, +}; +const zeroAddress = '0x0000000000000000000000000000000000000001'; + +async function main() { + const registry = new GithubRegistry(); + + const tokenConfig: WarpRouteDeployConfig = + Object.fromEntries( + await Promise.all( + chainsToDeploy.map( + async (chain): Promise<[string, TokenRouterConfig]> => { + const ret: [string, TokenRouterConfig] = [ + chain, + { + isNft: false, + type: + chain === lockboxChain + ? TokenType.XERC20Lockbox + : TokenType.XERC20, + token: chain === lockboxChain ? lockbox : xERC20, + owner: zeroAddress, + mailbox: (await registry.getChainAddresses(chain))!.mailbox, + interchainSecurityModule: { + type: IsmType.AGGREGATION, + threshold: 2, + modules: [ + { + type: IsmType.ROUTING, + owner: zeroAddress, + domains: buildAggregationIsmConfigs( + chain, + chainsToDeploy, + ezEthValidators, + ), + }, + { + type: IsmType.FALLBACK_ROUTING, + domains: {}, + owner: zeroAddress, + }, + ], + }, + }, + ]; + + return ret; + }, + ), + ), + ); + + const parsed = WarpRouteDeployConfigSchema.safeParse(tokenConfig); + + if (!parsed.success) { + console.dir(parsed.error.format(), { depth: null }); + return; + } + + writeFileSync( + 'renzo-warp-route-config.yaml', + yamlStringify(parsed.data, null, 2), + ); +} + +main().catch(console.error).then(console.log); diff --git a/typescript/sdk/src/ism/multisig.ts b/typescript/sdk/src/ism/multisig.ts index d2dd05756..83025f198 100644 --- a/typescript/sdk/src/ism/multisig.ts +++ b/typescript/sdk/src/ism/multisig.ts @@ -42,7 +42,7 @@ export const buildAggregationIsmConfigs = ( (chain, config): config is MultisigConfig => chain !== local && chains.includes(chain), ), - (_, config) => ({ + (_, config): AggregationIsmConfig => ({ type: IsmType.AGGREGATION, modules: [ { @@ -56,5 +56,5 @@ export const buildAggregationIsmConfigs = ( ], threshold: 1, }), - ) as ChainMap; + ); }; diff --git a/yarn.lock b/yarn.lock index fd561c5ec..c01b78027 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5852,6 +5852,7 @@ __metadata: prompts: "npm:^2.4.2" tsx: "npm:^4.7.1" typescript: "npm:5.3.3" + yaml: "npm:^2.4.5" yargs: "npm:^17.7.2" peerDependencies: "@ethersproject/abi": "*" @@ -26222,6 +26223,15 @@ __metadata: languageName: node linkType: hard +"yaml@npm:^2.4.5": + version: 2.4.5 + resolution: "yaml@npm:2.4.5" + bin: + yaml: bin.mjs + checksum: b09bf5a615a65276d433d76b8e34ad6b4c0320b85eb3f1a39da132c61ae6e2ff34eff4624e6458d96d49566c93cf43408ba5e568218293a8c6541a2006883f64 + languageName: node + linkType: hard + "yargs-parser@npm:13.1.2, yargs-parser@npm:^13.1.2": version: 13.1.2 resolution: "yargs-parser@npm:13.1.2" From 28f047739c424a658280fb9773bcbd3bde8bd10e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Noah=20Bayindirli=20=F0=9F=A5=82?= Date: Wed, 19 Jun 2024 16:46:48 -0400 Subject: [PATCH 48/73] fix(cli): make native token required in chain config (#3944) ### Description - addresses P1 in https://www.notion.so/hyperlanexyz/CLI-Bug-Bash-5fc0833e51034baeba3beba9319160ff?pvs=4 - NOTE: Mimicking this behaviour: https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/cli/src/utils/balances.ts#L23 - fixes https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/4008 #### TODO: - Once merged, this section of the quickstart guide should be removed: https://www.notion.so/hyperlanexyz/Hyperlane-Quickstart-Beta-4d9ff48720ee43fa8456a7d1517098dd?pvs=4#15534475fd8d4b398e51e9e0d527fb48 - cc @avious00 --- typescript/cli/src/config/chain.ts | 156 ++++++++++++++++++----------- typescript/cli/src/deploy/utils.ts | 2 +- 2 files changed, 99 insertions(+), 59 deletions(-) diff --git a/typescript/cli/src/config/chain.ts b/typescript/cli/src/config/chain.ts index bcedd179c..dcc26786a 100644 --- a/typescript/cli/src/config/chain.ts +++ b/typescript/cli/src/config/chain.ts @@ -88,64 +88,10 @@ export async function createChainConfig({ rpcUrls: [{ http: rpcUrl }], }; - const wantAdvancedConfig = await confirm({ - default: false, - message: - 'Do you want to set block or gas properties for this chain config?', - }); - 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), - }; - } - } - } + await addBlockOrGasConfig(metadata); + + await addNativeTokenConfig(metadata); + const parseResult = ChainMetadataSchema.safeParse(metadata); if (parseResult.success) { logGreen(`Chain config is valid, writing to registry`); @@ -158,3 +104,97 @@ export async function createChainConfig({ throw new Error('Invalid chain config'); } } + +async function addBlockOrGasConfig(metadata: ChainMetadata): Promise { + const wantBlockOrGasConfig = await confirm({ + default: false, + message: 'Do you want to set block or gas properties for this chain config', + }); + if (wantBlockOrGasConfig) { + await addBlockConfig(metadata); + await addGasConfig(metadata); + } +} + +async function addBlockConfig(metadata: ChainMetadata): Promise { + 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), + }; + } +} + +async function addGasConfig(metadata: ChainMetadata): Promise { + 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 (gwei):', + }); + const maxPriorityFeePerGas = await input({ + message: 'Enter the max priority fee per gas (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 (gwei):', + }); + metadata.transactionOverrides = { + gasPrice: BigInt(gasPrice) * BigInt(10 ** 9), + }; + } + } +} + +async function addNativeTokenConfig(metadata: ChainMetadata): Promise { + const wantNativeConfig = await confirm({ + default: false, + message: + 'Do you want to set native token properties for this chain config (defaults to ETH)', + }); + let symbol, name, decimals; + if (wantNativeConfig) { + symbol = await input({ + message: "Enter the native token's symbol:", + }); + name = await input({ + message: `Enter the native token's name:`, + }); + decimals = await input({ + message: "Enter the native token's decimals:", + }); + } + + metadata.nativeToken = { + symbol: symbol ?? 'ETH', + name: name ?? 'Ether', + decimals: decimals ? parseInt(decimals, 10) : 18, + }; +} diff --git a/typescript/cli/src/deploy/utils.ts b/typescript/cli/src/deploy/utils.ts index 46f3a6474..124fde09a 100644 --- a/typescript/cli/src/deploy/utils.ts +++ b/typescript/cli/src/deploy/utils.ts @@ -132,7 +132,7 @@ export async function completeDeploy( `\t- Gas required for ${command} ${ isDryRun ? 'dry-run' : 'deploy' } on ${chain}: ${ethers.utils.formatEther(balanceDelta)} ${ - multiProvider.getChainMetadata(chain).nativeToken?.symbol + multiProvider.getChainMetadata(chain).nativeToken?.symbol ?? 'ETH' }`, ); } From 5c8ba0b850c121f0571ebb4410acdcb6414c4202 Mon Sep 17 00:00:00 2001 From: Lee <6251863+ltyu@users.noreply.github.com> Date: Wed, 19 Jun 2024 16:51:17 -0400 Subject: [PATCH 49/73] fix(cli): Rename `hyperlane config create chain` -> `hyperlane registry init` (#3978) ### Description Renames `hyperlane config create chain` -> `hyperlane registry init` as according to https://www.notion.so/hyperlanexyz/Must-use-config-to-create-a-new-chain-instead-of-the-registry-command-f7b407b29a834f7288f20e4c08bc206e?pvs=4 ### Drive-by changes - Rename `configure` to `init` for all functions - Reorder functions alphabetically ### Backward compatibility No ### Testing Manual --- .changeset/giant-lies-whisper.md | 5 +++++ typescript/cli/src/commands/config.ts | 28 ++----------------------- typescript/cli/src/commands/core.ts | 6 +++--- typescript/cli/src/commands/registry.ts | 15 +++++++++++-- typescript/cli/src/commands/warp.ts | 6 +++--- 5 files changed, 26 insertions(+), 34 deletions(-) create mode 100644 .changeset/giant-lies-whisper.md diff --git a/.changeset/giant-lies-whisper.md b/.changeset/giant-lies-whisper.md new file mode 100644 index 000000000..374acdf49 --- /dev/null +++ b/.changeset/giant-lies-whisper.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/cli': minor +--- + +Rename hyperlane config create chain -> hyperlane registry init. Rename all `configure` to `init` diff --git a/typescript/cli/src/commands/config.ts b/typescript/cli/src/commands/config.ts index 2dd532249..7b145ca44 100644 --- a/typescript/cli/src/commands/config.ts +++ b/typescript/cli/src/commands/config.ts @@ -1,6 +1,6 @@ import { CommandModule } from 'yargs'; -import { createChainConfig, readChainConfigs } from '../config/chain.js'; +import { readChainConfigs } from '../config/chain.js'; import { readIsmConfig } from '../config/ism.js'; import { readMultisigConfig } from '../config/multisig.js'; import { readWarpRouteDeployConfig } from '../config/warp.js'; @@ -16,34 +16,10 @@ export const configCommand: CommandModule = { command: 'config', describe: 'Create or validate Hyperlane configs', builder: (yargs) => - yargs - .command(createCommand) - .command(validateCommand) - .version(false) - .demandCommand(), - handler: () => log('Command required'), -}; - -/** - * Create commands - */ -const createCommand: CommandModule = { - command: 'create', - describe: 'Create a new Hyperlane config', - builder: (yargs) => - yargs.command(createChainConfigCommand).version(false).demandCommand(), + yargs.command(validateCommand).version(false).demandCommand(), handler: () => log('Command required'), }; -const createChainConfigCommand: CommandModuleWithContext<{}> = { - command: 'chain', - describe: 'Create a new, minimal Hyperlane chain config (aka chain metadata)', - handler: async ({ context }) => { - await createChainConfig({ context }); - process.exit(0); - }, -}; - /** * Validate commands */ diff --git a/typescript/cli/src/commands/core.ts b/typescript/cli/src/commands/core.ts index 64c6de52e..54a5786f6 100644 --- a/typescript/cli/src/commands/core.ts +++ b/typescript/cli/src/commands/core.ts @@ -32,8 +32,8 @@ export const coreCommand: CommandModule = { describe: 'Manage core Hyperlane contracts & configs', builder: (yargs) => yargs - .command(configure) .command(deploy) + .command(init) .command(read) .version(false) .demandCommand(), @@ -82,11 +82,11 @@ export const deploy: CommandModuleWithWriteContext<{ }, }; -export const configure: CommandModuleWithContext<{ +export const init: CommandModuleWithContext<{ advanced: boolean; config: string; }> = { - command: 'configure', + command: 'init', describe: 'Create a core configuration, including ISMs and hooks.', builder: { advanced: { diff --git a/typescript/cli/src/commands/registry.ts b/typescript/cli/src/commands/registry.ts index d2679f496..9334064d5 100644 --- a/typescript/cli/src/commands/registry.ts +++ b/typescript/cli/src/commands/registry.ts @@ -1,6 +1,7 @@ import { CommandModule } from 'yargs'; import { createAgentConfig } from '../config/agent.js'; +import { createChainConfig } from '../config/chain.js'; import { CommandContext, CommandModuleWithContext } from '../context/types.js'; import { log, logBlue, logGray, logRed, logTable } from '../logger.js'; @@ -15,12 +16,13 @@ import { ChainType, ChainTypes } from './types.js'; */ export const registryCommand: CommandModule = { command: 'registry', - describe: 'View information about Hyperlane chains in a registry', + describe: 'Manage Hyperlane chains in a registry', builder: (yargs) => yargs - .command(listCommand) .command(addressesCommand) .command(createAgentConfigCommand) + .command(initCommand) + .command(listCommand) .version(false) .demandCommand(), handler: () => log('Command required'), @@ -140,3 +142,12 @@ const createAgentConfigCommand: CommandModuleWithContext<{ process.exit(0); }, }; + +const initCommand: CommandModuleWithContext<{}> = { + command: 'init', + describe: 'Create a new, minimal Hyperlane chain config (aka chain metadata)', + handler: async ({ context }) => { + await createChainConfig({ context }); + process.exit(0); + }, +}; diff --git a/typescript/cli/src/commands/warp.ts b/typescript/cli/src/commands/warp.ts index 1b61c7aff..905d2420f 100644 --- a/typescript/cli/src/commands/warp.ts +++ b/typescript/cli/src/commands/warp.ts @@ -43,8 +43,8 @@ export const warpCommand: CommandModule = { describe: 'Manage Hyperlane warp routes', builder: (yargs) => yargs - .command(configure) .command(deploy) + .command(init) .command(read) .command(send) .version(false) @@ -82,11 +82,11 @@ export const deploy: CommandModuleWithWriteContext<{ }, }; -export const configure: CommandModuleWithContext<{ +export const init: CommandModuleWithContext<{ advanced: boolean; out: string; }> = { - command: 'configure', + command: 'init', describe: 'Create a warp route configuration.', builder: { advanced: { From bd3ca9195aa38fb20e6e6ac01138a0516f4ef173 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Noah=20Bayindirli=20=F0=9F=A5=82?= Date: Wed, 19 Jun 2024 18:55:37 -0400 Subject: [PATCH 50/73] chore(cli): update ci-test to ci-advanced-test (#4011) ### Description - updates `ci-test.sh` name to `ci-advanced-test.sh` ### Drive-by changes - none ### Related issues - fixes https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/3995 ### Backward compatibility - yes ### Testing - implicit --- .changeset/sour-squids-buy.md | 5 +++++ .github/workflows/test-skipped.yml | 4 ++-- .github/workflows/test.yml | 4 ++-- typescript/cli/{ci-test.sh => ci-advanced-test.sh} | 2 +- 4 files changed, 10 insertions(+), 5 deletions(-) create mode 100644 .changeset/sour-squids-buy.md rename typescript/cli/{ci-test.sh => ci-advanced-test.sh} (99%) diff --git a/.changeset/sour-squids-buy.md b/.changeset/sour-squids-buy.md new file mode 100644 index 000000000..6bd0ae43c --- /dev/null +++ b/.changeset/sour-squids-buy.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/cli': minor +--- + +Updates ci-test.sh to ci-advanced-test.sh. diff --git a/.github/workflows/test-skipped.yml b/.github/workflows/test-skipped.yml index 3cff77784..48c7239d7 100644 --- a/.github/workflows/test-skipped.yml +++ b/.github/workflows/test-skipped.yml @@ -73,7 +73,7 @@ jobs: - name: Instant pass run: echo "e2e job passed" - cli-e2e: + cli-advanced-e2e: runs-on: ubuntu-latest if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.base_ref == 'main') || github.event_name == 'merge_group' strategy: @@ -84,7 +84,7 @@ jobs: - test-type: pi_with_core_chain steps: - name: Instant pass - run: echo "cli-e2e job passed" + run: echo "cli-advanced-e2e job passed" env-test: runs-on: ubuntu-latest diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6119eb04a..f7343f616 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -327,7 +327,7 @@ jobs: echo "All e2e-matrix jobs have completed." # You can add additional commands here to report the result as needed - cli-e2e: + cli-advanced-e2e: runs-on: larger-runner if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.base_ref == 'main' || github.base_ref == 'cli-2.0') || github.event_name == 'merge_group' needs: [yarn-build, checkout-registry] @@ -405,7 +405,7 @@ jobs: key: ${{ runner.os }}-cargo-cache-${{ hashFiles('./rust/Cargo.lock') }} - name: cli e2e tests - run: ./typescript/cli/ci-test.sh ${{ matrix.test-type }} + run: ./typescript/cli/ci-advanced-test.sh ${{ matrix.test-type }} env-test: runs-on: ubuntu-latest diff --git a/typescript/cli/ci-test.sh b/typescript/cli/ci-advanced-test.sh similarity index 99% rename from typescript/cli/ci-test.sh rename to typescript/cli/ci-advanced-test.sh index 3cdbcdf09..7f446146d 100755 --- a/typescript/cli/ci-test.sh +++ b/typescript/cli/ci-advanced-test.sh @@ -15,7 +15,7 @@ _main() { # with the routing over igp hook (which is closer to production deployment) TEST_TYPE=$1 if [ -z "$TEST_TYPE" ]; then - echo "Usage: ci-test.sh <$TEST_TYPE_PRESET_HOOK | $TEST_TYPE_CONFIGURED_HOOK | $TEST_TYPE_PI_CORE>" + echo "Usage: ci-advanced-test.sh <$TEST_TYPE_PRESET_HOOK | $TEST_TYPE_CONFIGURED_HOOK | $TEST_TYPE_PI_CORE>" exit 1 fi From 39f66df326e5328b7b1e467bcb98fdfb09b1c90c Mon Sep 17 00:00:00 2001 From: Paul Balaji Date: Fri, 21 Jun 2024 12:14:52 +0100 Subject: [PATCH 51/73] feat(ci): rust caching in `test` workflow (#4021) - brings https://github.com/hyperlane-xyz/hyperlane-monorepo/pull/4015 to cli-2.0 branch - cherry-pick e2e flake fix - ensure tests run on pushes to `cli-2.0` --------- Signed-off-by: Paul Balaji <10051819+paulbalaji@users.noreply.github.com> Co-authored-by: Daniel Savu <23065004+daniel-savu@users.noreply.github.com> --- .github/workflows/rust.yml | 6 +- .github/workflows/test.yml | 96 +++++++++++++++++++----- rust/utils/run-locally/src/invariants.rs | 10 ++- typescript/cli/ci-advanced-test.sh | 4 +- 4 files changed, 89 insertions(+), 27 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 0a2001630..27dc0403e 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -39,8 +39,7 @@ jobs: - name: rust cache uses: Swatinem/rust-cache@v2 with: - prefix-key: 'v2-rust' - shared-key: 'test' + prefix-key: "v3-rust" workspaces: | ./rust - name: Free disk space @@ -69,8 +68,7 @@ jobs: - name: rust cache uses: Swatinem/rust-cache@v2 with: - prefix-key: 'v2-rust' - shared-key: 'lint' + prefix-key: "v3-rust" workspaces: | ./rust - name: Free disk space diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f7343f616..2b4144793 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,9 +1,10 @@ name: test on: - # Triggers the workflow on push or pull request against main + # Triggers the workflow on pushes to main & cli-2.0 branches push: - branches: [main] + branches: [main, cli-2.0] + # Triggers on pull requests ignoring md files pull_request: branches: - '*' # run against all branches @@ -172,6 +173,7 @@ jobs: path: | ${{ env.REGISTRY_URI_ABSOLUTE }} key: hyperlane-registry-${{ github.event.pull_request.head.sha || github.sha }} + fail-on-cache-miss: true - name: Unit Tests run: yarn test:ci @@ -215,6 +217,7 @@ jobs: path: | ${{ env.REGISTRY_URI_ABSOLUTE }} key: hyperlane-registry-${{ github.event.pull_request.head.sha || github.sha }} + fail-on-cache-miss: true - name: Generate ${{ matrix.environment }} agent config run: | @@ -252,6 +255,14 @@ jobs: toolchain: stable profile: minimal + - name: rust cache + uses: Swatinem/rust-cache@v2 + with: + prefix-key: "v1-${{ runner.os }}-rust-cache" + shared-key: ${{ matrix.e2e-type }} + workspaces: | + ./rust + - name: Free disk space run: | # Based on https://github.com/actions/runner-images/issues/2840#issuecomment-790492173 @@ -292,23 +303,17 @@ jobs: path: | ${{ env.REGISTRY_URI_ABSOLUTE }} key: hyperlane-registry-${{ github.event.pull_request.head.sha || github.sha }} - - - name: cargo-cache - uses: actions/cache@v4 - with: - path: | - ~/.cargo - key: ${{ runner.os }}-cargo-cache-${{ hashFiles('./rust/Cargo.lock') }} + fail-on-cache-miss: true - name: agent tests with CosmWasm - run: cargo test --release --package run-locally --bin run-locally --features cosmos -- cosmos::test --nocapture + run: cargo test --release --package run-locally --bin run-locally --features cosmos test-utils -- cosmos::test --nocapture if: matrix.e2e-type == 'cosmwasm' working-directory: ./rust env: RUST_BACKTRACE: 'full' - name: agent tests excluding CosmWasm - run: cargo run --release --bin run-locally + run: cargo run --release --bin run-locally --features test-utils if: matrix.e2e-type == 'non-cosmwasm' working-directory: ./rust env: @@ -327,10 +332,60 @@ jobs: echo "All e2e-matrix jobs have completed." # You can add additional commands here to report the result as needed + prebuild-cli-e2e: + runs-on: larger-runner + if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.base_ref == 'main' || github.base_ref == 'cli-2.0') || github.event_name == 'merge_group' + needs: [checkout-registry] + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + submodules: recursive + + - name: setup rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + profile: minimal + + - name: rust cache + uses: Swatinem/rust-cache@v2 + with: + prefix-key: "v1-${{ runner.os }}-rust-cache" + shared-key: "cli-e2e" + workspaces: | + ./rust + + - 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: Build validator + run: cargo build --bin validator --features test-utils + working-directory: ./rust + env: + RUST_BACKTRACE: 'full' + + - name: Build relayer + run: cargo build --bin relayer --features test-utils + working-directory: ./rust + env: + RUST_BACKTRACE: 'full' + cli-advanced-e2e: runs-on: larger-runner if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.base_ref == 'main' || github.base_ref == 'cli-2.0') || github.event_name == 'merge_group' - needs: [yarn-build, checkout-registry] + needs: [yarn-build, prebuild-cli-e2e] strategy: matrix: include: @@ -356,6 +411,14 @@ jobs: toolchain: stable profile: minimal + - name: rust cache + uses: Swatinem/rust-cache@v2 + with: + prefix-key: "v1-${{ runner.os }}-rust-cache" + shared-key: "cli-e2e" + workspaces: | + ./rust + - name: Free disk space run: | # Based on https://github.com/actions/runner-images/issues/2840#issuecomment-790492173 @@ -396,13 +459,7 @@ jobs: path: | ${{ env.REGISTRY_URI_ABSOLUTE }} key: hyperlane-registry-${{ github.event.pull_request.head.sha || github.sha }} - - - name: cargo-cache - uses: actions/cache@v4 - with: - path: | - ~/.cargo - key: ${{ runner.os }}-cargo-cache-${{ hashFiles('./rust/Cargo.lock') }} + fail-on-cache-miss: true - name: cli e2e tests run: ./typescript/cli/ci-advanced-test.sh ${{ matrix.test-type }} @@ -447,6 +504,7 @@ jobs: path: | ${{ env.REGISTRY_URI_ABSOLUTE }} key: hyperlane-registry-${{ github.event.pull_request.head.sha || github.sha }} + fail-on-cache-miss: true - name: Fork test ${{ matrix.environment }} ${{ matrix.module }} ${{ matrix.chain }} deployment run: cd typescript/infra && ./fork.sh ${{ matrix.environment }} ${{ matrix.module }} ${{ matrix.chain }} diff --git a/rust/utils/run-locally/src/invariants.rs b/rust/utils/run-locally/src/invariants.rs index 2191f2ac8..18bc026c2 100644 --- a/rust/utils/run-locally/src/invariants.rs +++ b/rust/utils/run-locally/src/invariants.rs @@ -69,8 +69,14 @@ pub fn termination_invariants_met( .len(); // Zero insertion messages don't reach `submit` stage where gas is spent, so we only expect these logs for the other messages. - assert_eq!( - gas_expenditure_log_count as u32, total_messages_expected, + // TODO: Sometimes we find more logs than expected. This may either mean that gas is deducted twice for the same message due to a bug, + // or that submitting the message transaction fails for some messages. Figure out which is the case and convert this check to + // strict equality. + // EDIT: Having had a quick look, it seems like there are some legitimate reverts happening in the confirm step + // (`Transaction attempting to process message either reverted or was reorged`) + // in which case more gas expenditure logs than messages are expected. + assert!( + gas_expenditure_log_count as u32 >= total_messages_expected, "Didn't record gas payment for all delivered messages" ); diff --git a/typescript/cli/ci-advanced-test.sh b/typescript/cli/ci-advanced-test.sh index 7f446146d..c0fc99189 100755 --- a/typescript/cli/ci-advanced-test.sh +++ b/typescript/cli/ci-advanced-test.sh @@ -261,7 +261,7 @@ run_hyperlane_send_message() { run_validator() { echo -e "\nPre-building validator with cargo" - cargo build --bin validator + cargo build --bin validator --features test-utils # set some default agent env vars, used by both validators and relayer export HYP_CHAINS_${CHAIN1_CAPS}_BLOCKS_REORGPERIOD=0 @@ -315,7 +315,7 @@ run_validator() { run_relayer() { echo -e "\nPre-building relayer with cargo" - cargo build --bin relayer + cargo build --bin relayer --features test-utils echo "Running relayer" export CONFIG_FILES=/tmp/agent-config.json From e38d31685eef7b5b53e2c17879bd3306a34b1443 Mon Sep 17 00:00:00 2001 From: Lee <6251863+ltyu@users.noreply.github.com> Date: Fri, 21 Jun 2024 10:21:32 -0400 Subject: [PATCH 52/73] feat: Add smart provider log setter (#4007) ### Description - Add logic to enable setting of log levels to the SmartProvider. - Add logic to set log level to `silent` in the beginning of `deriveTokenType()`, and then resetting it after. ### Related issues - Fixes #4006 ### Backward compatibility Yes ### Testing Manual --- .changeset/shiny-cups-help.md | 5 +++ .../src/hook/EvmHookModule.hardhat-test.ts | 1 - .../providers/SmartProvider/SmartProvider.ts | 8 +++-- .../sdk/src/token/EvmERC20WarpRouteReader.ts | 33 +++++++++++++++++-- 4 files changed, 41 insertions(+), 6 deletions(-) create mode 100644 .changeset/shiny-cups-help.md diff --git a/.changeset/shiny-cups-help.md b/.changeset/shiny-cups-help.md new file mode 100644 index 000000000..dad4ee692 --- /dev/null +++ b/.changeset/shiny-cups-help.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/sdk': minor +--- + +Add logic to set smart provider log level to disable provider logs during Warp TokenType derive diff --git a/typescript/sdk/src/hook/EvmHookModule.hardhat-test.ts b/typescript/sdk/src/hook/EvmHookModule.hardhat-test.ts index fbc1446da..3a8094eb6 100644 --- a/typescript/sdk/src/hook/EvmHookModule.hardhat-test.ts +++ b/typescript/sdk/src/hook/EvmHookModule.hardhat-test.ts @@ -240,7 +240,6 @@ describe('EvmHookModule', async () => { async function createHook( config: HookConfig, ): Promise<{ hook: EvmHookModule; initialHookAddress: Address }> { - console.log('Creating hook with config: ', stringifyObject(config)); const hook = await EvmHookModule.create({ chain, config, diff --git a/typescript/sdk/src/providers/SmartProvider/SmartProvider.ts b/typescript/sdk/src/providers/SmartProvider/SmartProvider.ts index 9b6ab1cfb..f5c579a38 100644 --- a/typescript/sdk/src/providers/SmartProvider/SmartProvider.ts +++ b/typescript/sdk/src/providers/SmartProvider/SmartProvider.ts @@ -1,5 +1,5 @@ import { BigNumber, providers, utils } from 'ethers'; -import { Logger } from 'pino'; +import pino, { Logger } from 'pino'; import { raceWithContext, @@ -97,6 +97,10 @@ export class HyperlaneSmartProvider this.supportedMethods = [...supportedMethods.values()]; } + setLogLevel(level: pino.LevelWithSilentOrString) { + this.logger.level = level; + } + async getPriorityFee(): Promise { try { return BigNumber.from(await this.perform('maxPriorityFeePerGas', {})); @@ -271,7 +275,7 @@ export class HyperlaneSmartProvider providerResultPromises.push(resultPromise); pIndex += 1; } else if (result.status === ProviderStatus.Error) { - this.logger.warn( + this.logger.debug( `Error from provider #${pIndex}: ${result.error} - ${ !isLastProvider ? ' Triggering next provider.' : '' }`, diff --git a/typescript/sdk/src/token/EvmERC20WarpRouteReader.ts b/typescript/sdk/src/token/EvmERC20WarpRouteReader.ts index fc7a5326c..8f460b945 100644 --- a/typescript/sdk/src/token/EvmERC20WarpRouteReader.ts +++ b/typescript/sdk/src/token/EvmERC20WarpRouteReader.ts @@ -10,7 +10,12 @@ import { TokenRouterConfig, TokenType, } from '@hyperlane-xyz/sdk'; -import { Address, eqAddress, rootLogger } from '@hyperlane-xyz/utils'; +import { + Address, + eqAddress, + getLogLevel, + rootLogger, +} from '@hyperlane-xyz/utils'; import { DEFAULT_CONTRACT_READ_CONCURRENCY } from '../consts/concurrency.js'; import { EvmHookReader } from '../hook/EvmHookReader.js'; @@ -90,12 +95,20 @@ export class EvmERC20WarpRouteReader { }, }; + // Temporarily turn off SmartProvider logging + // Provider errors are expected because deriving will call methods that may not exist in the Bytecode + this.setSmartProviderLogLevel('silent'); + // First, try checking token specific methods - for (const [type, { factory, method }] of Object.entries(contractTypes)) { + for (const [tokenType, { factory, method }] of Object.entries( + contractTypes, + )) { try { const warpRoute = factory.connect(warpRouteAddress, this.provider); await warpRoute[method](); - return type as TokenType; + + this.setSmartProviderLogLevel(getLogLevel()); // returns to original level defined by rootLogger + return tokenType as TokenType; } catch (e) { continue; } @@ -114,6 +127,8 @@ export class EvmERC20WarpRouteReader { throw Error( `Error accessing token specific method, implying this is not a supported token.`, ); + } finally { + this.setSmartProviderLogLevel(getLogLevel()); // returns to original level defined by rootLogger } } @@ -203,4 +218,16 @@ export class EvmERC20WarpRouteReader { return { name, symbol, decimals, totalSupply: totalSupply.toString() }; } + + /** + * Conditionally sets the log level for a smart provider. + * + * @param level - The log level to set, e.g. 'debug', 'info', 'warn', 'error'. + */ + protected setSmartProviderLogLevel(level: string) { + if ('setLogLevel' in this.provider) { + //@ts-ignore + this.provider.setLogLevel(level); + } + } } From bb0c7afab7d4ca7d7d075b484be989d42c75fca8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Noah=20Bayindirli=20=F0=9F=A5=82?= <15343884+nbayindirli@users.noreply.github.com> Date: Fri, 21 Jun 2024 19:47:11 -0400 Subject: [PATCH 53/73] fix(cli): add units and explanation to protocol fee input (#4024) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Description - add units and explanation to protocol fee input - adds `inputWithInfo` to allow optional info developers can request with `i` ### Drive-by changes - none ### Related issues - fixes https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/4023 ### Backward compatibility - yes ### Testing - manual example output: ``` ➜ cli git:(cli-2.0) ✗ hl core init --registry $HOME/workplace/Hyperlane/hyperlane-registry --advanced Hyperlane CLI Hyperlane Core Configure ------------------------ Creating a new core deployment config... ? Detected owner address as 0x16F4898F47c085C41d7Cc6b1dc72B91EA617dcBb from signer, is this correct? yes Creating a new advanced ISM config WARNING: USE AT YOUR RISK. Advanced ISM configs require knowledge of different ISM types and how they work together topologically. If possible, use the basic ISM configs are recommended. ? Select ISM type trustedRelayerIsm Creating trustedRelayerIsm... ? Detected relayer address as 0x16F4898F47c085C41d7Cc6b1dc72B91EA617dcBb from signer, is this correct? yes Created trustedRelayerIsm! ? Select default hook type protocolFee Creating protocolFee... ? Detected owner address as 0x16F4898F47c085C41d7Cc6b1dc72B91EA617dcBb from signer, is this correct? yes ? Use this same address (0x16F4898F47c085C41d7Cc6b1dc72B91EA617dcBb) for the beneficiary? yes ? Enter max protocol fee for protocol fee hook (wei): [enter 'i' for more info] i The max protocol fee (ProtocolFee.MAX_PROTOCOL_FEE) is the maximum value the protocol fee on the ProtocolFee hook contract can ever be set to. Default is set to 100000000000000000 wei; between 0.001 and 0.1 wei is recommended. For more information, please visit https://docs.hyperlane.xyz. ? Enter max protocol fee for protocol fee hook (wei): [enter 'i' for more info] 0.1 ? Enter protocol fee for protocol fee hook (wei): [enter 'i' for more info] i The protocol fee is the fee collected by the beneficiary of the ProtocolFee hook for every transaction executed with this hook. Default is set to 0 wei; must be less than max protocol fee of 100000000000000000. For more information, please visit https://docs.hyperlane.xyz. ? Enter protocol fee for protocol fee hook (wei): [enter 'i' for more info] 0 Created protocolFee! ``` --- typescript/cli/src/config/chain.ts | 2 +- typescript/cli/src/config/core.ts | 2 +- typescript/cli/src/config/hooks.ts | 18 +++++----- typescript/cli/src/config/ism.ts | 6 ++-- typescript/cli/src/config/warp.ts | 6 ++-- typescript/cli/src/utils/chains.ts | 26 +------------- typescript/cli/src/utils/input.ts | 55 ++++++++++++++++++++++++++++++ 7 files changed, 71 insertions(+), 44 deletions(-) create mode 100644 typescript/cli/src/utils/input.ts diff --git a/typescript/cli/src/config/chain.ts b/typescript/cli/src/config/chain.ts index dcc26786a..f3ccbd068 100644 --- a/typescript/cli/src/config/chain.ts +++ b/typescript/cli/src/config/chain.ts @@ -10,8 +10,8 @@ import { ProtocolType } from '@hyperlane-xyz/utils'; import { CommandContext } from '../context/types.js'; import { errorRed, log, logBlue, logGreen } from '../logger.js'; -import { detectAndConfirmOrPrompt } from '../utils/chains.js'; import { readYamlOrJson } from '../utils/files.js'; +import { detectAndConfirmOrPrompt } from '../utils/input.js'; export function readChainConfigs(filePath: string) { log(`Reading file configs in ${filePath}`); diff --git a/typescript/cli/src/config/core.ts b/typescript/cli/src/config/core.ts index 776d35c96..0588185f4 100644 --- a/typescript/cli/src/config/core.ts +++ b/typescript/cli/src/config/core.ts @@ -4,8 +4,8 @@ import { CoreConfigSchema, HookConfig, IsmConfig } from '@hyperlane-xyz/sdk'; import { CommandContext } from '../context/types.js'; import { errorRed, log, logBlue, logGreen } from '../logger.js'; -import { detectAndConfirmOrPrompt } from '../utils/chains.js'; import { indentYamlOrJson, writeYamlOrJson } from '../utils/files.js'; +import { detectAndConfirmOrPrompt } from '../utils/input.js'; import { createHookConfig, diff --git a/typescript/cli/src/config/hooks.ts b/typescript/cli/src/config/hooks.ts index f5cb37451..18308b81a 100644 --- a/typescript/cli/src/config/hooks.ts +++ b/typescript/cli/src/config/hooks.ts @@ -19,11 +19,9 @@ import { import { CommandContext } from '../context/types.js'; import { errorRed, logBlue, logGreen, logRed } from '../logger.js'; -import { - detectAndConfirmOrPrompt, - runMultiChainSelectionStep, -} from '../utils/chains.js'; +import { runMultiChainSelectionStep } from '../utils/chains.js'; import { readYamlOrJson } from '../utils/files.js'; +import { detectAndConfirmOrPrompt, inputWithInfo } from '../utils/input.js'; import { callWithConfigCreationLogs } from './utils.js'; @@ -153,15 +151,17 @@ export const createProtocolFeeConfig = callWithConfigCreationLogs( // TODO: input in gwei, wei, etc const maxProtocolFee = advanced ? toWei( - await input({ - message: `Enter max protocol fee for protocol fee hook (in wei):`, + await inputWithInfo({ + message: `Enter max protocol fee for protocol fee hook (wei):`, + info: `The max protocol fee (ProtocolFee.MAX_PROTOCOL_FEE) is the maximum value the protocol fee on the ProtocolFee hook contract can ever be set to.\nDefault is set to ${MAX_PROTOCOL_FEE_DEFAULT} wei; between 0.001 and 0.1 wei is recommended.`, }), ) : MAX_PROTOCOL_FEE_DEFAULT; const protocolFee = advanced ? toWei( - await input({ - message: `Enter protocol fee for protocol fee hook (in wei):`, + await inputWithInfo({ + message: `Enter protocol fee for protocol fee hook (wei):`, + info: `The protocol fee is the fee collected by the beneficiary of the ProtocolFee hook for every transaction executed with this hook.\nDefault is set to 0 wei; must be less than max protocol fee of ${maxProtocolFee}.`, }), ) : PROTOCOL_FEE_DEFAULT; @@ -169,7 +169,7 @@ export const createProtocolFeeConfig = callWithConfigCreationLogs( errorRed( `Protocol fee (${protocolFee}) cannot be greater than max protocol fee (${maxProtocolFee}).`, ); - throw new Error('Invalid protocol fee.'); + throw new Error(`Invalid protocol fee (${protocolFee}).`); } return { type: HookType.PROTOCOL_FEE, diff --git a/typescript/cli/src/config/ism.ts b/typescript/cli/src/config/ism.ts index e2f69b3b5..eb976f2dd 100644 --- a/typescript/cli/src/config/ism.ts +++ b/typescript/cli/src/config/ism.ts @@ -19,11 +19,9 @@ import { logBoldUnderlinedRed, logRed, } from '../logger.js'; -import { - detectAndConfirmOrPrompt, - runMultiChainSelectionStep, -} from '../utils/chains.js'; +import { runMultiChainSelectionStep } from '../utils/chains.js'; import { readYamlOrJson } from '../utils/files.js'; +import { detectAndConfirmOrPrompt } from '../utils/input.js'; import { callWithConfigCreationLogs } from './utils.js'; diff --git a/typescript/cli/src/config/warp.ts b/typescript/cli/src/config/warp.ts index 759bf7c40..7bd71e747 100644 --- a/typescript/cli/src/config/warp.ts +++ b/typescript/cli/src/config/warp.ts @@ -16,15 +16,13 @@ import { Address, assert, objMap, promiseObjAll } from '@hyperlane-xyz/utils'; import { CommandContext } from '../context/types.js'; import { errorRed, log, logBlue, logGreen } from '../logger.js'; -import { - detectAndConfirmOrPrompt, - runMultiChainSelectionStep, -} from '../utils/chains.js'; +import { runMultiChainSelectionStep } from '../utils/chains.js'; import { indentYamlOrJson, readYamlOrJson, writeYamlOrJson, } from '../utils/files.js'; +import { detectAndConfirmOrPrompt } from '../utils/input.js'; import { createAdvancedIsmConfig } from './ism.js'; diff --git a/typescript/cli/src/utils/chains.ts b/typescript/cli/src/utils/chains.ts index ed147cc69..b22f7683a 100644 --- a/typescript/cli/src/utils/chains.ts +++ b/typescript/cli/src/utils/chains.ts @@ -1,4 +1,4 @@ -import { Separator, checkbox, confirm, input } from '@inquirer/prompts'; +import { Separator, checkbox } from '@inquirer/prompts'; import select from '@inquirer/select'; import chalk from 'chalk'; @@ -75,27 +75,3 @@ function handleNewChain(chainNames: string[]) { process.exit(0); } } - -export async function detectAndConfirmOrPrompt( - detect: () => Promise, - prompt: string, - label: string, - source?: string, -): Promise { - let detectedValue: string | undefined; - try { - detectedValue = await detect(); - if (detectedValue) { - const confirmed = await confirm({ - message: `Detected ${label} as ${detectedValue}${ - source ? ` from ${source}` : '' - }, is this correct?`, - }); - if (confirmed) { - return detectedValue; - } - } - // eslint-disable-next-line no-empty - } catch (e) {} - return input({ message: `${prompt} ${label}:`, default: detectedValue }); -} diff --git a/typescript/cli/src/utils/input.ts b/typescript/cli/src/utils/input.ts new file mode 100644 index 000000000..0f8c9ef66 --- /dev/null +++ b/typescript/cli/src/utils/input.ts @@ -0,0 +1,55 @@ +import { confirm, input } from '@inquirer/prompts'; + +import { logGray } from '../logger.js'; + +import { indentYamlOrJson } from './files.js'; + +export async function detectAndConfirmOrPrompt( + detect: () => Promise, + prompt: string, + label: string, + source?: string, +): Promise { + let detectedValue: string | undefined; + try { + detectedValue = await detect(); + if (detectedValue) { + const confirmed = await confirm({ + message: `Detected ${label} as ${detectedValue}${ + source ? ` from ${source}` : '' + }, is this correct?`, + }); + if (confirmed) { + return detectedValue; + } + } + // eslint-disable-next-line no-empty + } catch (e) {} + return input({ message: `${prompt} ${label}:`, default: detectedValue }); +} + +const INFO_COMMAND: string = 'i'; +const DOCS_NOTICE: string = + 'For more information, please visit https://docs.hyperlane.xyz.'; + +export async function inputWithInfo({ + message, + info = 'No additional information available.', + defaultAnswer, +}: { + message: string; + info?: string; + defaultAnswer?: string; +}): Promise { + let answer: string = ''; + do { + answer = await input({ + message: message.concat(` [enter '${INFO_COMMAND}' for more info]`), + default: defaultAnswer, + }); + answer = answer.trim().toLowerCase(); + const indentedInfo = indentYamlOrJson(`${info}\n${DOCS_NOTICE}\n`, 4); + if (answer === INFO_COMMAND) logGray(indentedInfo); + } while (answer === INFO_COMMAND); + return answer; +} From 7add7bf871105d58d1ffbb5d9dd8c6fd884e1147 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Noah=20Bayindirli=20=F0=9F=A5=82?= <15343884+nbayindirli@users.noreply.github.com> Date: Fri, 21 Jun 2024 20:12:03 -0400 Subject: [PATCH 54/73] chore(cli): format message stdout during hl send message (#4025) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Description - formats message stdout during hl send message ### Drive-by changes - none ### Related issues - P1 ### Backward compatibility - yes ### Testing - manual example output: ``` ➜ cli git:(06-20-fix_cli_add_units_and_explanation_to_protocol_fee_input) ✗ hl send message --relay --registry $HOME/workplace/Hyperlane/hyperlane-registry Hyperlane CLI ? Select the origin chain fuji ? Select the destination chain brown Running pre-flight checks for chains... ✅ Chains are valid ✅ Signer is valid ✅ Balances are sufficient Dispatching message Pending https://testnet.snowtrace.io/tx/0x5d0793052ad6df340242f332a218b14a8182b083bebbf5666faac83f7c20ed66 (waiting 3 blocks for confirmation) Sent message from fuji to 0xb2b1125501aA997b912A0fa32f74B3041C0c4FcB on brown. Message ID: 0x2ba918e23225a7dc67ec803cff998cbe9beefd45c7243422349035dcac5e20d0 Message: parsed: version: 3 nonce: 2605 origin: 43113 sender: "0x00000000000000000000000016f4898f47c085c41d7cc6b1dc72b91ea617dcbb" destination: 6700087 recipient: "0x000000000000000000000000b2b1125501aa997b912a0fa32f74b3041c0c4fcb" body: "0x48656c6c6f21" originChain: fuji destinationChain: brown id: "0x2ba918e23225a7dc67ec803cff998cbe9beefd45c7243422349035dcac5e20d0" message: "0x0300000a2d0000a86900000000000000000000000016f4898f47c085c41d7cc6b1d\ c72b91ea617dcbb00663c37000000000000000000000000b2b1125501aa997b912a0fa32f74b3\ 041c0c4fcb48656c6c6f21" ``` --- typescript/cli/src/deploy/core.ts | 9 +++++++-- typescript/cli/src/deploy/warp.ts | 5 ++--- typescript/cli/src/send/message.ts | 5 ++++- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/typescript/cli/src/deploy/core.ts b/typescript/cli/src/deploy/core.ts index d2d8b3587..fb40d93b5 100644 --- a/typescript/cli/src/deploy/core.ts +++ b/typescript/cli/src/deploy/core.ts @@ -1,9 +1,12 @@ +import { stringify as yamlStringify } from 'yaml'; + import { ChainName, CoreConfig, EvmCoreModule } from '@hyperlane-xyz/sdk'; import { MINIMUM_CORE_DEPLOY_GAS } from '../consts.js'; import { WriteCommandContext } from '../context/types.js'; -import { logBlue } from '../logger.js'; +import { log, logBlue, logGreen } from '../logger.js'; import { runSingleChainSelectionStep } from '../utils/chains.js'; +import { indentYamlOrJson } from '../utils/files.js'; import { completeDeploy, @@ -83,5 +86,7 @@ export async function runCoreDeploy({ // @TODO implement writeAgentConfig } - logBlue('Deployment is complete!'); + + logGreen('✅ Core contract deployments complete:\n'); + log(indentYamlOrJson(yamlStringify(deployedAddresses, null, 2), 4)); } diff --git a/typescript/cli/src/deploy/warp.ts b/typescript/cli/src/deploy/warp.ts index 707415ae1..58758eac8 100644 --- a/typescript/cli/src/deploy/warp.ts +++ b/typescript/cli/src/deploy/warp.ts @@ -143,15 +143,14 @@ async function executeDeploy(params: DeployParams) { const deployedContracts = await deployer.deploy(modifiedConfig); + const warpCoreConfig = await getWarpCoreConfig(params, deployedContracts); logGreen('✅ Warp contract deployments complete'); - const warpCoreConfig = await getWarpCoreConfig(params, deployedContracts); if (!isDryRun) { - log('Writing deployment artifacts'); + log('Writing deployment artifacts...'); await registry.addWarpRoute(warpCoreConfig); } log(indentYamlOrJson(yamlStringify(warpCoreConfig, null, 2), 4)); - logBlue('Deployment is complete!'); } async function deployAndResolveWarpIsm( diff --git a/typescript/cli/src/send/message.ts b/typescript/cli/src/send/message.ts index 729f3be04..ca303f6eb 100644 --- a/typescript/cli/src/send/message.ts +++ b/typescript/cli/src/send/message.ts @@ -1,3 +1,5 @@ +import { stringify as yamlStringify } from 'yaml'; + import { ChainName, HyperlaneCore } from '@hyperlane-xyz/sdk'; import { addressToBytes32, timeout } from '@hyperlane-xyz/utils'; @@ -6,6 +8,7 @@ import { CommandContext, WriteCommandContext } from '../context/types.js'; import { runPreflightChecksForChains } from '../deploy/utils.js'; import { errorRed, log, logBlue, logGreen } from '../logger.js'; import { runSingleChainSelectionStep } from '../utils/chains.js'; +import { indentYamlOrJson } from '../utils/files.js'; export async function sendTestMessage({ context, @@ -103,7 +106,7 @@ async function executeDelivery({ ); logBlue(`Sent message from ${origin} to ${recipient} on ${destination}.`); logBlue(`Message ID: ${message.id}`); - log(`Message: ${JSON.stringify(message)}`); + log(`Message:\n${indentYamlOrJson(yamlStringify(message, null, 2), 4)}`); if (selfRelay) { log('Attempting self-relay of message'); From 91d2f6d56a3757846eab8b3a14093e87753da9a5 Mon Sep 17 00:00:00 2001 From: Paul Balaji Date: Mon, 24 Jun 2024 13:03:16 +0100 Subject: [PATCH 55/73] chore: include 10% gas estimate bump in EvmIsmModule (#3957) - in the multiprovider we already do a 10% bump on the estimated gas, as a cover for unpredictable gas estimation - we recently found that the ism factories do not do this, which caused problems in a recent hyperlane core deployment - added this into the ism factories on `main` - so we are ensuring that this change is properly reflected in the new ism module as well --- typescript/sdk/src/deploy/EvmModuleDeployer.ts | 11 ++++++++++- typescript/sdk/src/ism/EvmIsmModule.ts | 15 +++++++++++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/typescript/sdk/src/deploy/EvmModuleDeployer.ts b/typescript/sdk/src/deploy/EvmModuleDeployer.ts index ba8a16f83..fca9e4d4a 100644 --- a/typescript/sdk/src/deploy/EvmModuleDeployer.ts +++ b/typescript/sdk/src/deploy/EvmModuleDeployer.ts @@ -258,11 +258,20 @@ export class EvmModuleDeployer { `Deploying new ${threshold} of ${values.length} address set to ${chain}`, ); const overrides = multiProvider.getTransactionOverrides(chain); - const hash = await factory['deploy(address[],uint8)']( + + // estimate gas + const estimatedGas = await factory.estimateGas['deploy(address[],uint8)']( values, threshold, overrides, ); + + // add 10% buffer + const hash = await factory['deploy(address[],uint8)'](values, threshold, { + ...overrides, + gasLimit: estimatedGas.add(estimatedGas.div(10)), // 10% buffer + }); + await multiProvider.handleTx(chain, hash); } else { logger.debug( diff --git a/typescript/sdk/src/ism/EvmIsmModule.ts b/typescript/sdk/src/ism/EvmIsmModule.ts index 62e814fc5..e9037d1a8 100644 --- a/typescript/sdk/src/ism/EvmIsmModule.ts +++ b/typescript/sdk/src/ism/EvmIsmModule.ts @@ -525,14 +525,25 @@ export class EvmIsmModule extends HyperlaneModule< signer, ); - // deploying new domain routing ISM - const tx = await domainRoutingIsmFactory.deploy( + // estimate gas + const estimatedGas = await domainRoutingIsmFactory.estimateGas.deploy( owner, domainIds, submoduleAddresses, overrides, ); + // deploying new domain routing ISM, add 10% buffer + const tx = await domainRoutingIsmFactory.deploy( + owner, + domainIds, + submoduleAddresses, + { + ...overrides, + gasLimit: estimatedGas.add(estimatedGas.div(10)), // 10% buffer + }, + ); + const receipt = await this.multiProvider.handleTx(this.args.chain, tx); const dispatchLogs = findMatchingLogEvents( receipt.logs, From 3941c994164d17ad31a5c1c6529514776e9d51aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Noah=20Bayindirli=20=F0=9F=A5=82?= <15343884+nbayindirli@users.noreply.github.com> Date: Mon, 24 Jun 2024 09:09:23 -0400 Subject: [PATCH 56/73] chore(cli): display new chain config after registry init (#4038) ### Description - displays new chain config after `hl registry init` ### Drive-by changes - none ### Related issues - fixes https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/4039 ### Backward compatibility - yes ### Testing - manual --- typescript/cli/src/config/chain.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/typescript/cli/src/config/chain.ts b/typescript/cli/src/config/chain.ts index f3ccbd068..924bbd325 100644 --- a/typescript/cli/src/config/chain.ts +++ b/typescript/cli/src/config/chain.ts @@ -1,5 +1,6 @@ import { confirm, input } from '@inquirer/prompts'; import { ethers } from 'ethers'; +import { stringify as yamlStringify } from 'yaml'; import { ChainMetadata, @@ -10,7 +11,7 @@ import { ProtocolType } from '@hyperlane-xyz/utils'; import { CommandContext } from '../context/types.js'; import { errorRed, log, logBlue, logGreen } from '../logger.js'; -import { readYamlOrJson } from '../utils/files.js'; +import { indentYamlOrJson, readYamlOrJson } from '../utils/files.js'; import { detectAndConfirmOrPrompt } from '../utils/input.js'; export function readChainConfigs(filePath: string) { @@ -94,7 +95,9 @@ export async function createChainConfig({ const parseResult = ChainMetadataSchema.safeParse(metadata); if (parseResult.success) { - logGreen(`Chain config is valid, writing to registry`); + logGreen(`Chain config is valid, writing to registry:`); + const metadataYaml = yamlStringify(metadata, null, 2); + log(indentYamlOrJson(metadataYaml, 4)); await context.registry.updateChain({ chainName: metadata.name, metadata }); } else { errorRed( From cc0ea73360095c92796789eba832f82f6d16abbd Mon Sep 17 00:00:00 2001 From: Paul Balaji Date: Mon, 24 Jun 2024 15:28:40 +0100 Subject: [PATCH 57/73] feat: pin monorepo to a specific registry version (#4041) - feat: pin monorepo to a specific registry version (cherry-pick #4031) - drive-by agent config update --------- Signed-off-by: Paul Balaji <10051819+paulbalaji@users.noreply.github.com> --- .github/actions/checkout-registry/action.yml | 28 +++++ .github/workflows/monorepo-docker.yml | 12 ++- .github/workflows/test-skipped.yml | 6 -- .github/workflows/test.yml | 101 +++---------------- .registryrc | 1 + Dockerfile | 9 +- rust/config/mainnet_config.json | 6 +- 7 files changed, 59 insertions(+), 104 deletions(-) create mode 100644 .github/actions/checkout-registry/action.yml create mode 100644 .registryrc diff --git a/.github/actions/checkout-registry/action.yml b/.github/actions/checkout-registry/action.yml new file mode 100644 index 000000000..929ea7539 --- /dev/null +++ b/.github/actions/checkout-registry/action.yml @@ -0,0 +1,28 @@ +name: 'Checkout Registry' +description: 'Checkout the hyperlane-registry repository and move it to the parent directory' +inputs: + registry_version: + description: 'Override the version of the hyperlane-registry to checkout' +runs: + using: 'composite' + steps: + - name: Read .registryrc if registry_version not provided + shell: bash + run: | + if [ -z "${{ inputs.registry_version }}" ]; then + REGISTRY_VERSION=$(cat .registryrc) + echo "REGISTRY_VERSION=$REGISTRY_VERSION" >> $GITHUB_ENV + else + echo "REGISTRY_VERSION=${{ inputs.registry_version }}" >> $GITHUB_ENV + fi + + - name: Checkout hyperlane-registry + uses: actions/checkout@v4 + with: + repository: hyperlane-xyz/hyperlane-registry + ref: ${{ env.REGISTRY_VERSION }} + path: ./hyperlane-registry + + - name: Move hyperlane-registry to parent directory + shell: bash + run: mv ./hyperlane-registry ../ diff --git a/.github/workflows/monorepo-docker.yml b/.github/workflows/monorepo-docker.yml index 629e08acb..67d4f554f 100644 --- a/.github/workflows/monorepo-docker.yml +++ b/.github/workflows/monorepo-docker.yml @@ -10,9 +10,11 @@ on: - 'typescript/infra/**' - 'Dockerfile' - '.dockerignore' + concurrency: group: build-push-monorepo-${{ github.ref }} cancel-in-progress: true + jobs: check-env: runs-on: ubuntu-latest @@ -66,6 +68,13 @@ jobs: registry: gcr.io username: _json_key password: ${{ secrets.GCLOUD_SERVICE_KEY }} + + - name: Read .registryrc + shell: bash + run: | + REGISTRY_VERSION=$(cat .registryrc) + echo "REGISTRY_VERSION=$REGISTRY_VERSION" >> $GITHUB_ENV + - name: Build and push uses: docker/build-push-action@v5 with: @@ -76,6 +85,5 @@ jobs: labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max - # To always fetch the latest registry, we use the date as the cache key build-args: | - REGISTRY_CACHE=${{ steps.taggen.outputs.TAG_DATE }} + REGISTRY_COMMIT=${{ env.REGISTRY_VERSION }} diff --git a/.github/workflows/test-skipped.yml b/.github/workflows/test-skipped.yml index 48c7239d7..cba9dc2f3 100644 --- a/.github/workflows/test-skipped.yml +++ b/.github/workflows/test-skipped.yml @@ -28,12 +28,6 @@ jobs: - name: Instant pass run: echo "yarn-build job passed" - checkout-registry: - runs-on: ubuntu-latest - steps: - - name: Instant pass - run: echo "checkout-registry job passed" - lint-prettier: runs-on: ubuntu-latest steps: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2b4144793..ade6f6c26 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,8 +24,6 @@ env: LOG_FORMAT: PRETTY CARGO_TERM_COLOR: always RUST_BACKTRACE: full - # Alongside the monorepo in the directory above the $GITHUB_WORKSPACE. - REGISTRY_URI: ${{ github.workspace }}/../hyperlane-registry jobs: yarn-install: @@ -87,31 +85,6 @@ jobs: - name: build run: yarn build - checkout-registry: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - repository: hyperlane-xyz/hyperlane-registry - ref: main - path: ./hyperlane-registry - - # Put alongside the monorepo in the directory above the $GITHUB_WORKSPACE. - # actions/checkout doesn't allow you to checkout a repository outside of the workspace. - # See https://github.com/actions/checkout/issues/197. - - run: mv ./hyperlane-registry ../ - - # A workaround for relative paths not being supported by actions/cache. - # See https://github.com/actions/upload-artifact/issues/176#issuecomment-1367855630. - - run: echo "REGISTRY_URI_ABSOLUTE=$(realpath $REGISTRY_URI)" >> $GITHUB_ENV - - - name: registry-cache - uses: actions/cache@v4 - with: - path: | - ${{ env.REGISTRY_URI_ABSOLUTE }} - key: hyperlane-registry-${{ github.event.pull_request.head.sha || github.sha }} - lint-prettier: runs-on: ubuntu-latest needs: [yarn-install] @@ -144,7 +117,7 @@ jobs: yarn-test: runs-on: ubuntu-latest - needs: [yarn-build, checkout-registry] + needs: [yarn-build] steps: - uses: actions/checkout@v4 with: @@ -163,24 +136,15 @@ jobs: !./rust key: ${{ github.event.pull_request.head.sha || github.sha }} - # A workaround for relative paths not being supported by actions/cache. - # See https://github.com/actions/upload-artifact/issues/176#issuecomment-1367855630. - - run: echo "REGISTRY_URI_ABSOLUTE=$(realpath $REGISTRY_URI)" >> $GITHUB_ENV - - - name: registry-cache - uses: actions/cache@v4 - with: - path: | - ${{ env.REGISTRY_URI_ABSOLUTE }} - key: hyperlane-registry-${{ github.event.pull_request.head.sha || github.sha }} - fail-on-cache-miss: true + - name: Checkout registry + uses: ./.github/actions/checkout-registry - name: Unit Tests run: yarn test:ci agent-configs: runs-on: ubuntu-latest - needs: [yarn-build, checkout-registry] + needs: [yarn-build] strategy: fail-fast: false matrix: @@ -207,17 +171,8 @@ jobs: !./rust key: ${{ github.event.pull_request.head.sha || github.sha }} - # A workaround for relative paths not being supported by actions/cache. - # See https://github.com/actions/upload-artifact/issues/176#issuecomment-1367855630. - - run: echo "REGISTRY_URI_ABSOLUTE=$(realpath $REGISTRY_URI)" >> $GITHUB_ENV - - - name: registry-cache - uses: actions/cache@v4 - with: - path: | - ${{ env.REGISTRY_URI_ABSOLUTE }} - key: hyperlane-registry-${{ github.event.pull_request.head.sha || github.sha }} - fail-on-cache-miss: true + - name: Checkout registry + uses: ./.github/actions/checkout-registry - name: Generate ${{ matrix.environment }} agent config run: | @@ -232,7 +187,7 @@ jobs: e2e-matrix: runs-on: larger-runner if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.base_ref == 'main') || github.event_name == 'merge_group' - needs: [yarn-build, checkout-registry] + needs: [yarn-build] strategy: matrix: e2e-type: [cosmwasm, non-cosmwasm] @@ -293,17 +248,8 @@ jobs: !./rust key: ${{ github.event.pull_request.head.sha || github.sha }} - # A workaround for relative paths not being supported by actions/cache. - # See https://github.com/actions/upload-artifact/issues/176#issuecomment-1367855630. - - run: echo "REGISTRY_URI_ABSOLUTE=$(realpath $REGISTRY_URI)" >> $GITHUB_ENV - - - name: registry-cache - uses: actions/cache@v4 - with: - path: | - ${{ env.REGISTRY_URI_ABSOLUTE }} - key: hyperlane-registry-${{ github.event.pull_request.head.sha || github.sha }} - fail-on-cache-miss: true + - name: Checkout registry + uses: ./.github/actions/checkout-registry - name: agent tests with CosmWasm run: cargo test --release --package run-locally --bin run-locally --features cosmos test-utils -- cosmos::test --nocapture @@ -335,7 +281,6 @@ jobs: prebuild-cli-e2e: runs-on: larger-runner if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.base_ref == 'main' || github.base_ref == 'cli-2.0') || github.event_name == 'merge_group' - needs: [checkout-registry] steps: - uses: actions/checkout@v4 with: @@ -449,24 +394,15 @@ jobs: !./rust key: ${{ github.event.pull_request.head.sha || github.sha }} - # A workaround for relative paths not being supported by actions/cache. - # See https://github.com/actions/upload-artifact/issues/176#issuecomment-1367855630. - - run: echo "REGISTRY_URI_ABSOLUTE=$(realpath $REGISTRY_URI)" >> $GITHUB_ENV - - - name: registry-cache - uses: actions/cache@v4 - with: - path: | - ${{ env.REGISTRY_URI_ABSOLUTE }} - key: hyperlane-registry-${{ github.event.pull_request.head.sha || github.sha }} - fail-on-cache-miss: true + - name: Checkout registry + uses: ./.github/actions/checkout-registry - name: cli e2e tests run: ./typescript/cli/ci-advanced-test.sh ${{ matrix.test-type }} env-test: runs-on: ubuntu-latest - needs: [yarn-build, checkout-registry] + needs: [yarn-build] strategy: fail-fast: false matrix: @@ -494,17 +430,8 @@ jobs: !./rust key: ${{ github.event.pull_request.head.sha || github.sha }} - # A workaround for relative paths not being supported by actions/cache. - # See https://github.com/actions/upload-artifact/issues/176#issuecomment-1367855630. - - run: echo "REGISTRY_URI_ABSOLUTE=$(realpath $REGISTRY_URI)" >> $GITHUB_ENV - - - name: registry-cache - uses: actions/cache@v4 - with: - path: | - ${{ env.REGISTRY_URI_ABSOLUTE }} - key: hyperlane-registry-${{ github.event.pull_request.head.sha || github.sha }} - fail-on-cache-miss: true + - name: Checkout registry + uses: ./.github/actions/checkout-registry - name: Fork test ${{ matrix.environment }} ${{ matrix.module }} ${{ matrix.chain }} deployment run: cd typescript/infra && ./fork.sh ${{ matrix.environment }} ${{ matrix.module }} ${{ matrix.chain }} diff --git a/.registryrc b/.registryrc new file mode 100644 index 000000000..826e14246 --- /dev/null +++ b/.registryrc @@ -0,0 +1 @@ +v2.1.1 diff --git a/Dockerfile b/Dockerfile index 763e1186f..1a03f74d8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,8 +29,7 @@ COPY solidity ./solidity RUN yarn build ENV REGISTRY_URI="/hyperlane-registry" -# To allow us to avoid caching the registry clone, we use a build-time arg to force -# the below steps to be re-run if this arg is changed. -ARG REGISTRY_CACHE="default" - -RUN git clone https://github.com/hyperlane-xyz/hyperlane-registry.git "$REGISTRY_URI" +ARG REGISTRY_COMMIT="main" +RUN git clone https://github.com/hyperlane-xyz/hyperlane-registry.git "$REGISTRY_URI" \ + && cd "$REGISTRY_URI" \ + && git checkout "$REGISTRY_COMMIT" diff --git a/rust/config/mainnet_config.json b/rust/config/mainnet_config.json index e87c823c9..9c4d88372 100644 --- a/rust/config/mainnet_config.json +++ b/rust/config/mainnet_config.json @@ -638,7 +638,8 @@ }, "injective": { "bech32Prefix": "inj", - "blockExplorers": [], + "blockExplorers": [ + ], "blocks": { "confirmations": 1, "estimateBlockTime": 1, @@ -1377,9 +1378,6 @@ }, { "http": "https://zetachain-athens-evm.blockpi.network/v1/rpc/public" - }, - { - "http": "https://zetachain-mainnet-archive.allthatnode.com:8545" } ], "staticAggregationHookFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", From 47cba135223c2c565b3f261107fb11cc004c0c95 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Mon, 24 Jun 2024 15:29:32 +0100 Subject: [PATCH 58/73] chore: Upgrade registry to 2.1.1 (#4035) --- typescript/cli/package.json | 2 +- typescript/helloworld/package.json | 2 +- typescript/infra/package.json | 2 +- yarn.lock | 14 +++++++------- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/typescript/cli/package.json b/typescript/cli/package.json index f08b4ce68..67b5e384b 100644 --- a/typescript/cli/package.json +++ b/typescript/cli/package.json @@ -5,7 +5,7 @@ "dependencies": { "@aws-sdk/client-kms": "^3.577.0", "@aws-sdk/client-s3": "^3.577.0", - "@hyperlane-xyz/registry": "^2.1.0", + "@hyperlane-xyz/registry": "^2.1.1", "@hyperlane-xyz/sdk": "3.15.0", "@hyperlane-xyz/utils": "3.15.0", "@inquirer/prompts": "^3.0.0", diff --git a/typescript/helloworld/package.json b/typescript/helloworld/package.json index b9f9ff06c..62bc36a0a 100644 --- a/typescript/helloworld/package.json +++ b/typescript/helloworld/package.json @@ -4,7 +4,7 @@ "version": "3.15.0", "dependencies": { "@hyperlane-xyz/core": "3.15.0", - "@hyperlane-xyz/registry": "^2.1.0", + "@hyperlane-xyz/registry": "^2.1.1", "@hyperlane-xyz/sdk": "3.15.0", "@openzeppelin/contracts-upgradeable": "^4.9.3", "ethers": "^5.7.2" diff --git a/typescript/infra/package.json b/typescript/infra/package.json index e45e312e2..86440c27e 100644 --- a/typescript/infra/package.json +++ b/typescript/infra/package.json @@ -14,7 +14,7 @@ "@ethersproject/providers": "^5.7.2", "@google-cloud/secret-manager": "^5.5.0", "@hyperlane-xyz/helloworld": "3.15.0", - "@hyperlane-xyz/registry": "^2.1.0", + "@hyperlane-xyz/registry": "^2.1.1", "@hyperlane-xyz/sdk": "3.15.0", "@hyperlane-xyz/utils": "3.15.0", "@nomiclabs/hardhat-etherscan": "^3.0.3", diff --git a/yarn.lock b/yarn.lock index c01b78027..d5633e9b9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5683,7 +5683,7 @@ __metadata: "@aws-sdk/client-s3": "npm:^3.577.0" "@ethersproject/abi": "npm:*" "@ethersproject/providers": "npm:*" - "@hyperlane-xyz/registry": "npm:^2.1.0" + "@hyperlane-xyz/registry": "npm:^2.1.1" "@hyperlane-xyz/sdk": "npm:3.15.0" "@hyperlane-xyz/utils": "npm:3.15.0" "@inquirer/prompts": "npm:^3.0.0" @@ -5775,7 +5775,7 @@ __metadata: resolution: "@hyperlane-xyz/helloworld@workspace:typescript/helloworld" dependencies: "@hyperlane-xyz/core": "npm:3.15.0" - "@hyperlane-xyz/registry": "npm:^2.1.0" + "@hyperlane-xyz/registry": "npm:^2.1.1" "@hyperlane-xyz/sdk": "npm:3.15.0" "@nomiclabs/hardhat-ethers": "npm:^2.2.3" "@nomiclabs/hardhat-waffle": "npm:^2.0.6" @@ -5824,7 +5824,7 @@ __metadata: "@ethersproject/providers": "npm:^5.7.2" "@google-cloud/secret-manager": "npm:^5.5.0" "@hyperlane-xyz/helloworld": "npm:3.15.0" - "@hyperlane-xyz/registry": "npm:^2.1.0" + "@hyperlane-xyz/registry": "npm:^2.1.1" "@hyperlane-xyz/sdk": "npm:3.15.0" "@hyperlane-xyz/utils": "npm:3.15.0" "@nomiclabs/hardhat-ethers": "npm:^2.2.3" @@ -5877,13 +5877,13 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/registry@npm:^2.1.0": - version: 2.1.0 - resolution: "@hyperlane-xyz/registry@npm:2.1.0" +"@hyperlane-xyz/registry@npm:^2.1.1": + version: 2.1.1 + resolution: "@hyperlane-xyz/registry@npm:2.1.1" dependencies: yaml: "npm:^2" zod: "npm:^3.21.2" - checksum: cfcd441dcbb4886a4ecd90dffaeb7a0fd81c0a126423b23cf100cd554470fe88ceb35b41271d779f0390c42293fd2c799742eeff6dd42ca42f3c799a8e88b96b + checksum: e0e1f36b9bc43dfb1e4f7c512091b31c6f1b4046687d076fc16c91fd9204af1bdbe7898c8c29ada3c6830f6961bb5aa0933357b73d500595689171f99b992922 languageName: node linkType: hard From c81721e59c03616ce08db37e0ec4e0736095dec2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Noah=20Bayindirli=20=F0=9F=A5=82?= <15343884+nbayindirli@users.noreply.github.com> Date: Tue, 25 Jun 2024 11:02:10 -0400 Subject: [PATCH 59/73] feat(cli): display formatted deployment plan to confirm core deploy (#4050) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Description - Displays formatted deployment plan to confirm core deploy ### Drive-by changes - none ### Related issues - Fixes P1 ### Backward compatibility - yes ### Testing - Output: ``` ➜ cli git:(cli-2.0) ✗ hl core deploy --registry $HOME/workplace/Hyperlane/hyperlane-registry Hyperlane CLI Hyperlane permissionless deployment ------------------------------------------------ ? Select chain to connect: alpha Deployment plan =============== Transaction signer and owner of new contracts: 0x16F4898F47c085C41d7Cc6b1dc72B91EA617dcBb Deploying core contracts to network: alpha ┌────────────────────────┬──────────────────────────────────────────────┐ │ (index) │ Values │ ├────────────────────────┼──────────────────────────────────────────────┤ │ Name │ 'alpha' │ │ Display Name │ 'Alpha' │ │ Chain ID │ 75904 │ │ Domain ID │ 75904 │ │ Protocol │ 'ethereum' │ │ JSON RPC URL │ 'https://alpha-tk.rpc.caldera.xyz/http' │ │ Native Token: Symbol │ 'ETH' │ │ Native Token: Name │ 'Ether' │ │ Native Token: Decimals │ 18 │ └────────────────────────┴──────────────────────────────────────────────┘ Note: There are several contracts required for each chain, but contracts in your provided registries will be skipped. ? Is this deployment plan correct? (Y/n) ``` --- typescript/cli/src/deploy/utils.ts | 36 +++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/typescript/cli/src/deploy/utils.ts b/typescript/cli/src/deploy/utils.ts index 124fde09a..2d92db996 100644 --- a/typescript/cli/src/deploy/utils.ts +++ b/typescript/cli/src/deploy/utils.ts @@ -3,6 +3,7 @@ import { BigNumber, ethers } from 'ethers'; import { ChainMap, + ChainMetadata, ChainName, IsmConfig, MultisigConfig, @@ -12,7 +13,14 @@ import { Address, ProtocolType } from '@hyperlane-xyz/utils'; import { parseIsmConfig } from '../config/ism.js'; import { WriteCommandContext } from '../context/types.js'; -import { log, logBlue, logGray, logGreen, logPink } from '../logger.js'; +import { + log, + logBlue, + logGray, + logGreen, + logPink, + logTable, +} from '../logger.js'; import { gasBalancesAreSufficient } from '../utils/balances.js'; import { ENV } from '../utils/env.js'; import { assertSigner } from '../utils/keys.js'; @@ -63,15 +71,19 @@ export async function runDeployPlanStep({ context: WriteCommandContext; chain: ChainName; }) { - const { signer, skipConfirmation } = context; + const { signer, chainMetadata: chainMetadataMap, skipConfirmation } = context; const address = await signer.getAddress(); logBlue('\nDeployment plan'); logGray('==============='); - log(`Transaction signer and owner of new contracts will be ${address}`); - log(`Deploying to ${chain}`); + log(`Transaction signer and owner of new contracts: ${address}`); + log(`Deploying core contracts to network: ${chain}`); + const transformedChainMetadata = transformChainMetadataForDisplay( + chainMetadataMap[chain], + ); + logTable(transformedChainMetadata); log( - `There are several contracts required for each chain but contracts in your provided registries will be skipped`, + `Note: There are several contracts required for each chain, but contracts in your provided registries will be skipped.`, ); if (skipConfirmation) return; @@ -143,3 +155,17 @@ export async function completeDeploy( export function toUpperCamelCase(string: string) { return string.charAt(0).toUpperCase() + string.slice(1); } + +function transformChainMetadataForDisplay(chainMetadata: ChainMetadata) { + return { + Name: chainMetadata.name, + 'Display Name': chainMetadata.displayName, + 'Chain ID': chainMetadata.chainId, + 'Domain ID': chainMetadata.domainId, + Protocol: chainMetadata.protocol, + 'JSON RPC URL': chainMetadata.rpcUrls[0].http, + 'Native Token: Symbol': chainMetadata.nativeToken?.symbol, + 'Native Token: Name': chainMetadata.nativeToken?.name, + 'Native Token: Decimals': chainMetadata.nativeToken?.decimals, + }; +} From 4dd2651ee9be0824bac5b7da0442a1a77209b034 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Tue, 25 Jun 2024 11:14:11 -0400 Subject: [PATCH 60/73] feat: add xerc20 limit lookups to warp read (#4020) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Description Adds xerc20 mint and burn limit fetching to `warp read --symbol` ### Backward compatibility Yes ### Testing Manual ![Screenshot 2024-06-20 at 3 19 13 PM](https://github.com/hyperlane-xyz/hyperlane-monorepo/assets/3020995/9692e3fd-eecb-4758-af39-04639639b482) --- .changeset/five-owls-learn.md | 5 +++ typescript/cli/src/commands/warp.ts | 48 ++++++++++++++++++++++++++++- 2 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 .changeset/five-owls-learn.md diff --git a/.changeset/five-owls-learn.md b/.changeset/five-owls-learn.md new file mode 100644 index 000000000..222e2e65f --- /dev/null +++ b/.changeset/five-owls-learn.md @@ -0,0 +1,5 @@ +--- +"@hyperlane-xyz/cli": patch +--- + +Add xerc20 limit lookups to warp read diff --git a/typescript/cli/src/commands/warp.ts b/typescript/cli/src/commands/warp.ts index 905d2420f..7a1367779 100644 --- a/typescript/cli/src/commands/warp.ts +++ b/typescript/cli/src/commands/warp.ts @@ -1,9 +1,16 @@ +import { ethers } from 'ethers'; import { stringify as yamlStringify } from 'yaml'; import { CommandModule } from 'yargs'; +import { + HypXERC20Lockbox__factory, + HypXERC20__factory, + IXERC20__factory, +} from '@hyperlane-xyz/core'; import { ChainMap, EvmERC20WarpRouteReader, + TokenStandard, WarpCoreConfig, } from '@hyperlane-xyz/sdk'; import { objMap, promiseObjAll } from '@hyperlane-xyz/utils'; @@ -18,7 +25,7 @@ import { } from '../context/types.js'; import { evaluateIfDryRunFailure } from '../deploy/dry-run.js'; import { runWarpRouteDeploy } from '../deploy/warp.js'; -import { log, logGray, logGreen, logRed } from '../logger.js'; +import { log, logGray, logGreen, logRed, logTable } from '../logger.js'; import { sendTestTransfer } from '../send/transfer.js'; import { indentYamlOrJson, writeYamlOrJson } from '../utils/files.js'; import { selectRegistryWarpRoute } from '../utils/tokens.js'; @@ -144,6 +151,45 @@ export const read: CommandModuleWithContext<{ context.registry, symbol, ); + + // TODO: merge with XERC20TokenAdapter and WarpRouteReader + const xerc20Limits = await Promise.all( + warpCoreConfig.tokens + .filter( + (t) => + t.standard === TokenStandard.EvmHypXERC20 || + t.standard === TokenStandard.EvmHypXERC20Lockbox, + ) + .map(async (t) => { + const provider = multiProvider.getProvider(t.chainName); + const router = t.addressOrDenom!; + const xerc20Address = + t.standard === TokenStandard.EvmHypXERC20Lockbox + ? await HypXERC20Lockbox__factory.connect( + router, + provider, + ).xERC20() + : await HypXERC20__factory.connect( + router, + provider, + ).wrappedToken(); + + const xerc20 = IXERC20__factory.connect(xerc20Address, provider); + const mint = await xerc20.mintingCurrentLimitOf(router); + const burn = await xerc20.burningCurrentLimitOf(router); + + const formattedLimits = objMap({ mint, burn }, (_, v) => + ethers.utils.formatUnits(v, t.decimals), + ); + + return [t.chainName, formattedLimits]; + }), + ); + if (xerc20Limits.length > 0) { + logGray('xERC20 Limits:'); + logTable(Object.fromEntries(xerc20Limits)); + } + addresses = Object.fromEntries( warpCoreConfig.tokens.map((t) => [t.chainName, t.addressOrDenom!]), ); From aecb65a986b34fbc78ddc1c10ed341ab67d73676 Mon Sep 17 00:00:00 2001 From: Daniel Savu <23065004+daniel-savu@users.noreply.github.com> Date: Wed, 26 Jun 2024 13:49:11 +0100 Subject: [PATCH 61/73] feat: persist status labels (#4043) ### Description Builds on top of https://github.com/hyperlane-xyz/hyperlane-monorepo/pull/4003 to persist updates to an operation's status to the db, so we no longer default to `FirstPrepareAttempt` upon restarting the relayer ### Drive-by changes Moves `hyperlane_db` from `hyperlane_base` to `hyperlane_core` so it can be added to the `PendingOperation` trait. `PendingMessage` already stores a reference to the db in `ctx`, so I figured it doesn't break any assumptions. ### Related issues ### Backward compatibility Yes ### Testing Unit tests encoding of `PendingOperationStatus`, need to manually tests e2e --- rust/Cargo.lock | 43 +++++++------ rust/agents/relayer/src/msg/op_queue.rs | 4 ++ rust/agents/relayer/src/msg/op_submitter.rs | 11 +++- .../agents/relayer/src/msg/pending_message.rs | 17 +++++ rust/hyperlane-base/Cargo.toml | 2 - .../src/db/rocks/hyperlane_db.rs | 10 ++- rust/hyperlane-base/src/lib.rs | 6 +- rust/hyperlane-base/src/settings/mod.rs | 3 - .../src/traits/pending_operation.rs | 62 +++++++++++++++++-- 9 files changed, 123 insertions(+), 35 deletions(-) diff --git a/rust/Cargo.lock b/rust/Cargo.lock index a68d609bb..94449ecd5 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -1094,13 +1094,13 @@ dependencies = [ [[package]] name = "clang-sys" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" dependencies = [ "glob", "libc", - "libloading 0.8.1", + "libloading 0.8.4", ] [[package]] @@ -3908,6 +3908,12 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -4244,7 +4250,8 @@ dependencies = [ "serde_json", "sha3 0.10.8", "solana-sdk", - "strum 0.25.0", + "strum 0.26.3", + "strum_macros 0.26.4", "thiserror", "tiny-keccak 2.0.2", "tokio", @@ -5175,12 +5182,12 @@ dependencies = [ [[package]] name = "libloading" -version = "0.8.1" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161" +checksum = "e310b3a6b5907f99202fcdb4960ff45b93735d7c7d96b760fcff8db2dc0e103d" dependencies = [ "cfg-if", - "windows-sys 0.48.0", + "windows-targets 0.52.0", ] [[package]] @@ -5266,9 +5273,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.14" +version = "1.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "295c17e837573c8c821dbaeb3cceb3d745ad082f7572191409e69cbc1b3fd050" +checksum = "5e143b5e666b2695d28f6bca6497720813f699c9602dd7f5cac91008b8ada7f9" dependencies = [ "cc", "pkg-config", @@ -6462,9 +6469,9 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a41cf62165e97c7f814d2221421dbb9afcbcdb0a88068e5ea206e19951c2cbb5" +checksum = "8d3928fb5db768cb86f891ff014f0144589297e3c6a1aba6ed7cecfdace270c7" dependencies = [ "proc-macro2 1.0.76", "syn 2.0.48", @@ -7042,7 +7049,7 @@ dependencies = [ "reqwest", "serde", "serde_json", - "strum 0.25.0", + "strum 0.26.3", "thiserror", "tokio", "tokio-metrics", @@ -9518,11 +9525,11 @@ dependencies = [ [[package]] name = "strum" -version = "0.25.0" +version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" dependencies = [ - "strum_macros 0.25.3", + "strum_macros 0.26.4", ] [[package]] @@ -9552,11 +9559,11 @@ dependencies = [ [[package]] name = "strum_macros" -version = "0.25.3" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ - "heck 0.4.1", + "heck 0.5.0", "proc-macro2 1.0.76", "quote 1.0.35", "rustversion", diff --git a/rust/agents/relayer/src/msg/op_queue.rs b/rust/agents/relayer/src/msg/op_queue.rs index 07869b0de..41adc685f 100644 --- a/rust/agents/relayer/src/msg/op_queue.rs +++ b/rust/agents/relayer/src/msg/op_queue.rs @@ -166,6 +166,10 @@ mod test { todo!() } + fn retrieve_status_from_db(&self) -> Option { + todo!() + } + fn get_operation_labels(&self) -> (String, String) { Default::default() } diff --git a/rust/agents/relayer/src/msg/op_submitter.rs b/rust/agents/relayer/src/msg/op_submitter.rs index 014070dc1..ec345be72 100644 --- a/rust/agents/relayer/src/msg/op_submitter.rs +++ b/rust/agents/relayer/src/msg/op_submitter.rs @@ -186,9 +186,14 @@ async fn receive_task( // make sure things are getting wired up correctly; if this works in testing it // should also be valid in production. debug_assert_eq!(*op.destination_domain(), domain); - prepare_queue - .push(op, Some(PendingOperationStatus::FirstPrepareAttempt)) - .await; + let status = op.retrieve_status_from_db().unwrap_or_else(|| { + trace!( + ?op, + "No status found for message, defaulting to FirstPrepareAttempt" + ); + PendingOperationStatus::FirstPrepareAttempt + }); + prepare_queue.push(op, Some(status)).await; } } diff --git a/rust/agents/relayer/src/msg/pending_message.rs b/rust/agents/relayer/src/msg/pending_message.rs index 4887e192c..760a791cb 100644 --- a/rust/agents/relayer/src/msg/pending_message.rs +++ b/rust/agents/relayer/src/msg/pending_message.rs @@ -127,6 +127,13 @@ impl PendingOperation for PendingMessage { } fn set_status(&mut self, status: PendingOperationStatus) { + if let Err(e) = self + .ctx + .origin_db + .store_status_by_message_id(&self.message.id(), &self.status) + { + warn!(message_id = ?self.message.id(), err = %e, status = %self.status, "Persisting `status` failed for message"); + } self.status = status; } @@ -142,6 +149,16 @@ impl PendingOperation for PendingMessage { self.ctx.destination_mailbox.domain() } + fn retrieve_status_from_db(&self) -> Option { + match self.ctx.origin_db.retrieve_status_by_message_id(&self.id()) { + Ok(status) => status, + Err(e) => { + warn!(error=?e, "Failed to retrieve status for message"); + None + } + } + } + fn app_context(&self) -> Option { self.app_context.clone() } diff --git a/rust/hyperlane-base/Cargo.toml b/rust/hyperlane-base/Cargo.toml index 36a8432bb..0564c06a7 100644 --- a/rust/hyperlane-base/Cargo.toml +++ b/rust/hyperlane-base/Cargo.toml @@ -46,7 +46,6 @@ url.workspace = true warp.workspace = true ya-gcp.workspace = true - backtrace = { workspace = true, optional = true } backtrace-oneline = { path = "../utils/backtrace-oneline", optional = true } @@ -58,7 +57,6 @@ 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 = "*" diff --git a/rust/hyperlane-base/src/db/rocks/hyperlane_db.rs b/rust/hyperlane-base/src/db/rocks/hyperlane_db.rs index b4323613a..6b83e051a 100644 --- a/rust/hyperlane-base/src/db/rocks/hyperlane_db.rs +++ b/rust/hyperlane-base/src/db/rocks/hyperlane_db.rs @@ -7,7 +7,7 @@ use hyperlane_core::{ GasPaymentKey, HyperlaneDomain, HyperlaneLogStore, HyperlaneMessage, HyperlaneSequenceAwareIndexerStoreReader, HyperlaneWatermarkedLogStore, Indexed, InterchainGasExpenditure, InterchainGasPayment, InterchainGasPaymentMeta, LogMeta, - MerkleTreeInsertion, H256, + MerkleTreeInsertion, PendingOperationStatus, H256, }; use super::{ @@ -27,6 +27,7 @@ const HIGHEST_SEEN_MESSAGE_NONCE: &str = "highest_seen_message_nonce_"; const GAS_PAYMENT_FOR_MESSAGE_ID: &str = "gas_payment_sequence_for_message_id_v2_"; const GAS_PAYMENT_META_PROCESSED: &str = "gas_payment_meta_processed_v3_"; const GAS_EXPENDITURE_FOR_MESSAGE_ID: &str = "gas_expenditure_for_message_id_v2_"; +const STATUS_BY_MESSAGE_ID: &str = "status_by_message_id_"; const PENDING_MESSAGE_RETRY_COUNT_FOR_MESSAGE_ID: &str = "pending_message_retry_count_for_message_id_"; const MERKLE_TREE_INSERTION: &str = "merkle_tree_insertion_"; @@ -501,6 +502,13 @@ make_store_and_retrieve!(pub(self), dispatched_block_number_by_nonce, MESSAGE_DI make_store_and_retrieve!(pub, processed_by_nonce, NONCE_PROCESSED, u32, bool); make_store_and_retrieve!(pub(self), processed_by_gas_payment_meta, GAS_PAYMENT_META_PROCESSED, InterchainGasPaymentMeta, bool); make_store_and_retrieve!(pub(self), interchain_gas_expenditure_data_by_message_id, GAS_EXPENDITURE_FOR_MESSAGE_ID, H256, InterchainGasExpenditureData); +make_store_and_retrieve!( + pub, + status_by_message_id, + STATUS_BY_MESSAGE_ID, + H256, + PendingOperationStatus +); make_store_and_retrieve!(pub(self), interchain_gas_payment_data_by_gas_payment_key, GAS_PAYMENT_FOR_MESSAGE_ID, GasPaymentKey, InterchainGasPaymentData); make_store_and_retrieve!(pub(self), gas_payment_by_sequence, GAS_PAYMENT_BY_SEQUENCE, u32, InterchainGasPayment); make_store_and_retrieve!(pub(self), gas_payment_block_by_sequence, GAS_PAYMENT_BY_SEQUENCE, u32, u64); diff --git a/rust/hyperlane-base/src/lib.rs b/rust/hyperlane-base/src/lib.rs index ce6843e58..7ff1fc235 100644 --- a/rust/hyperlane-base/src/lib.rs +++ b/rust/hyperlane-base/src/lib.rs @@ -12,6 +12,9 @@ pub mod settings; mod agent; pub use agent::*; +/// The local database used by agents +pub mod db; + pub mod metrics; pub use metrics::*; @@ -28,8 +31,5 @@ pub use traits::*; mod types; pub use types::*; -/// Hyperlane database utils -pub mod db; - #[cfg(feature = "oneline-eyre")] pub mod oneline_eyre; diff --git a/rust/hyperlane-base/src/settings/mod.rs b/rust/hyperlane-base/src/settings/mod.rs index aa7bee534..6eb127953 100644 --- a/rust/hyperlane-base/src/settings/mod.rs +++ b/rust/hyperlane-base/src/settings/mod.rs @@ -65,9 +65,6 @@ pub use base::*; pub use chains::*; pub use checkpoint_syncer::*; -/// Export this so they don't need to import paste. -#[doc(hidden)] -pub use paste; pub use signers::*; pub use trace::*; diff --git a/rust/hyperlane-core/src/traits/pending_operation.rs b/rust/hyperlane-core/src/traits/pending_operation.rs index 476ec9e4b..ed6148bb8 100644 --- a/rust/hyperlane-core/src/traits/pending_operation.rs +++ b/rust/hyperlane-core/src/traits/pending_operation.rs @@ -1,12 +1,14 @@ +use serde::{Deserialize, Serialize}; use std::{ cmp::Ordering, fmt::{Debug, Display}, + io::Write, time::{Duration, Instant}, }; use crate::{ - ChainResult, FixedPointNumber, HyperlaneDomain, HyperlaneMessage, TryBatchAs, TxOutcome, H256, - U256, + ChainResult, Decode, Encode, FixedPointNumber, HyperlaneDomain, HyperlaneMessage, + HyperlaneProtocolError, TryBatchAs, TxOutcome, H256, U256, }; use async_trait::async_trait; use num::CheckedDiv; @@ -50,6 +52,9 @@ pub trait PendingOperation: Send + Sync + Debug + TryBatchAs { /// The domain this originates from. fn origin_domain_id(&self) -> u32; + /// Get the operation status from the local db, if there is one + fn retrieve_status_from_db(&self) -> Option; + /// The domain this operation will take place on. fn destination_domain(&self) -> &HyperlaneDomain; @@ -114,8 +119,10 @@ pub trait PendingOperation: Send + Sync + Debug + TryBatchAs { fn set_retries(&mut self, retries: u32); } -#[derive(Debug, Display, Clone)] +#[derive(Debug, Display, Clone, Serialize, Deserialize, PartialEq)] /// Status of a pending operation +/// WARNING: This enum is serialized to JSON and stored in the database, so to keep backwards compatibility, we shouldn't remove or rename any variants. +/// Adding new variants is fine. pub enum PendingOperationStatus { /// The operation is ready to be prepared for the first time, or has just been loaded from storage FirstPrepareAttempt, @@ -129,8 +136,38 @@ pub enum PendingOperationStatus { Confirm(ConfirmReason), } -#[derive(Display, Debug, Clone)] +impl Encode for PendingOperationStatus { + fn write_to(&self, writer: &mut W) -> std::io::Result + where + W: Write, + { + // Serialize to JSON and write to the writer, to avoid having to implement the encoding manually + let serialized = serde_json::to_vec(self) + .map_err(|_| std::io::Error::new(std::io::ErrorKind::Other, "Failed to serialize"))?; + writer.write(&serialized) + } +} + +impl Decode for PendingOperationStatus { + fn read_from(reader: &mut R) -> Result + where + R: std::io::Read, + Self: Sized, + { + // Deserialize from JSON and read from the reader, to avoid having to implement the encoding / decoding manually + serde_json::from_reader(reader).map_err(|err| { + HyperlaneProtocolError::IoError(std::io::Error::new( + std::io::ErrorKind::Other, + format!("Failed to deserialize. Error: {}", err), + )) + }) + } +} + +#[derive(Display, Debug, Clone, Serialize, Deserialize, PartialEq)] /// Reasons for repreparing an operation +/// WARNING: This enum is serialized to JSON and stored in the database, so to keep backwards compatibility, we shouldn't remove or rename any variants. +/// Adding new variants is fine. pub enum ReprepareReason { #[strum(to_string = "Error checking message delivery status")] /// Error checking message delivery status @@ -167,8 +204,10 @@ pub enum ReprepareReason { RevertedOrReorged, } -#[derive(Display, Debug, Clone)] +#[derive(Display, Debug, Clone, Serialize, Deserialize, PartialEq)] /// Reasons for repreparing an operation +/// WARNING: This enum is serialized to JSON and stored in the database, so to keep backwards compatibility, we shouldn't remove or rename any variants. +/// Adding new variants is fine. pub enum ConfirmReason { #[strum(to_string = "Submitted by this relayer")] /// Operation was submitted by this relayer @@ -274,3 +313,16 @@ pub enum PendingOperationResult { /// Send this message straight to the confirm queue Confirm(ConfirmReason), } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_encoding_pending_operation_status() { + let status = PendingOperationStatus::Retry(ReprepareReason::CouldNotFetchMetadata); + let encoded = status.to_vec(); + let decoded = PendingOperationStatus::read_from(&mut &encoded[..]).unwrap(); + assert_eq!(status, decoded); + } +} From b05ae38ac0c72aa3183a596d19bec7c2791fcaaa Mon Sep 17 00:00:00 2001 From: Lee <6251863+ltyu@users.noreply.github.com> Date: Wed, 26 Jun 2024 10:04:14 -0400 Subject: [PATCH 62/73] fix(cli): RPC errors while sending test messages (#4055) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Description This PR fixes RPC errors when trying to send a test message using the CLI for older chains that don't have tokenType (e.g., `hyperlane send message --relay --origin fuji --destination betaop`) Additional details: https://discord.com/channels/935678348330434570/1254873902480359435/1255215832959811687 ### Backward compatibility Yes ### Testing - tested with `hyperlane warp deploy` and then `hyperlane warp send --relay --warp $HOME/.hyperlane/deployments/warp_routes/ETH/alfajores-betaop-config.yaml` - tested `hyperlane send message --relay --origin fuji --destination betaop` - tested `hyperlane send message --relay --origin alfrajores --destination betaop` - tested `hyperlane send message --relay --origin holesky --destination betaop` - tested `hyperlane core read --mailbox 0xEf9F292fcEBC3848bF4bB92a96a04F9ECBb78E59 --chain alfajores ` --------- Co-authored-by: Noah Bayindirli 🥂 --- .changeset/calm-eels-care.md | 6 + typescript/cli/src/deploy/core.ts | 2 - typescript/sdk/src/core/HyperlaneCore.ts | 5 +- typescript/sdk/src/hook/EvmHookModule.ts | 16 ++- typescript/sdk/src/hook/EvmHookReader.test.ts | 21 ++++ typescript/sdk/src/hook/EvmHookReader.ts | 105 ++++++++++++------ 6 files changed, 117 insertions(+), 38 deletions(-) create mode 100644 .changeset/calm-eels-care.md diff --git a/.changeset/calm-eels-care.md b/.changeset/calm-eels-care.md new file mode 100644 index 000000000..a7911cd16 --- /dev/null +++ b/.changeset/calm-eels-care.md @@ -0,0 +1,6 @@ +--- +'@hyperlane-xyz/cli': minor +'@hyperlane-xyz/sdk': minor +--- + +Gracefully handle RPC failures during warp send & fix deriving hook error that prevents warp and core test messages on the cli. diff --git a/typescript/cli/src/deploy/core.ts b/typescript/cli/src/deploy/core.ts index fb40d93b5..7e585d4b9 100644 --- a/typescript/cli/src/deploy/core.ts +++ b/typescript/cli/src/deploy/core.ts @@ -83,8 +83,6 @@ export async function runCoreDeploy({ chainName: chain, addresses: deployedAddresses, }); - - // @TODO implement writeAgentConfig } logGreen('✅ Core contract deployments complete:\n'); diff --git a/typescript/sdk/src/core/HyperlaneCore.ts b/typescript/sdk/src/core/HyperlaneCore.ts index fe4fd7d68..fbe3ea498 100644 --- a/typescript/sdk/src/core/HyperlaneCore.ts +++ b/typescript/sdk/src/core/HyperlaneCore.ts @@ -8,6 +8,7 @@ import { AddressBytes32, ProtocolType, addressToBytes32, + assert, bytes32ToAddress, messageId, objFilter, @@ -116,7 +117,9 @@ export class HyperlaneCore extends HyperlaneApp { const originChain = this.getOrigin(message); const hookReader = new EvmHookReader(this.multiProvider, originChain); const address = await this.getHookAddress(message); - return hookReader.deriveHookConfig(address); + const hookConfig = await hookReader.deriveHookConfig(address); + assert(hookConfig, `No hook config found for ${address}.`); + return hookConfig; } async buildMetadata( diff --git a/typescript/sdk/src/hook/EvmHookModule.ts b/typescript/sdk/src/hook/EvmHookModule.ts index 29ea95534..612043dd4 100644 --- a/typescript/sdk/src/hook/EvmHookModule.ts +++ b/typescript/sdk/src/hook/EvmHookModule.ts @@ -20,6 +20,7 @@ import { Address, ProtocolType, addressToBytes32, + assert, configDeepEquals, rootLogger, } from '@hyperlane-xyz/utils'; @@ -108,9 +109,18 @@ export class EvmHookModule extends HyperlaneModule< } public async read(): Promise { - return typeof this.args.config === 'string' - ? this.args.addresses.deployedHook - : this.reader.deriveHookConfig(this.args.addresses.deployedHook); + if (typeof this.args.config === 'string') { + return this.args.addresses.deployedHook; + } else { + const hookConfig = await this.reader.deriveHookConfig( + this.args.addresses.deployedHook, + ); + assert( + hookConfig, + `No hook config found for ${this.args.addresses.deployedHook}`, + ); + return hookConfig; + } } public async update(_config: HookConfig): Promise { diff --git a/typescript/sdk/src/hook/EvmHookReader.test.ts b/typescript/sdk/src/hook/EvmHookReader.test.ts index e9d45e428..5650b12ac 100644 --- a/typescript/sdk/src/hook/EvmHookReader.test.ts +++ b/typescript/sdk/src/hook/EvmHookReader.test.ts @@ -186,6 +186,27 @@ describe('EvmHookReader', () => { expect(config).to.deep.equal(hookConfig); }); + it('should return an empty config if deriving fails', async () => { + const mockAddress = generateRandomAddress(); + const mockOwner = generateRandomAddress(); + + // Mocking the connect method + returned what we need from contract object + const mockContract = { + // No type + owner: sandbox.stub().resolves(mockOwner), + }; + sandbox + .stub(MerkleTreeHook__factory, 'connect') + .returns(mockContract as unknown as MerkleTreeHook); + sandbox + .stub(IPostDispatchHook__factory, 'connect') + .returns(mockContract as unknown as IPostDispatchHook); + + // top-level method infers hook type + const hookConfig = await evmHookReader.deriveHookConfig(mockAddress); + expect(hookConfig).to.be.undefined; + }); + /* Testing for more nested hook types can be done manually by reading from existing contracts onchain. Examples of nested hook types include: diff --git a/typescript/sdk/src/hook/EvmHookReader.ts b/typescript/sdk/src/hook/EvmHookReader.ts index e4fa97198..b57fa8ede 100644 --- a/typescript/sdk/src/hook/EvmHookReader.ts +++ b/typescript/sdk/src/hook/EvmHookReader.ts @@ -20,6 +20,7 @@ import { assert, concurrentMap, eqAddress, + getLogLevel, rootLogger, } from '@hyperlane-xyz/utils'; @@ -45,7 +46,9 @@ import { export type DerivedHookConfig = WithAddress>; export interface HookReader { - deriveHookConfig(address: Address): Promise>; + deriveHookConfig( + address: Address, + ): Promise | undefined>; deriveMerkleTreeConfig( address: Address, ): Promise>; @@ -84,35 +87,51 @@ export class EvmHookReader implements HookReader { this.provider = multiProvider.getProvider(chain); } - async deriveHookConfig(address: Address): Promise { - const hook = IPostDispatchHook__factory.connect(address, this.provider); - const onchainHookType: OnchainHookType = await hook.hookType(); - this.logger.debug('Deriving HookConfig', { address, onchainHookType }); - - switch (onchainHookType) { - case OnchainHookType.ROUTING: - return this.deriveDomainRoutingConfig(address); - case OnchainHookType.AGGREGATION: - return this.deriveAggregationConfig(address); - case OnchainHookType.MERKLE_TREE: - return this.deriveMerkleTreeConfig(address); - case OnchainHookType.INTERCHAIN_GAS_PAYMASTER: - return this.deriveIgpConfig(address); - case OnchainHookType.FALLBACK_ROUTING: - return this.deriveFallbackRoutingConfig(address); - case OnchainHookType.PAUSABLE: - return this.derivePausableConfig(address); - case OnchainHookType.PROTOCOL_FEE: - return this.deriveProtocolFeeConfig(address); - // ID_AUTH_ISM could be OPStackHook, ERC5164Hook or LayerZeroV2Hook - // For now assume it's OP_STACK - case OnchainHookType.ID_AUTH_ISM: - return this.deriveOpStackConfig(address); - default: - throw new Error( - `Unsupported HookType: ${OnchainHookType[onchainHookType]}`, - ); + async deriveHookConfig( + address: Address, + ): Promise { + let onchainHookType = undefined; + try { + const hook = IPostDispatchHook__factory.connect(address, this.provider); + this.logger.debug('Deriving HookConfig', { address }); + + // Temporarily turn off SmartProvider logging + // Provider errors are expected because deriving will call methods that may not exist in the Bytecode + this.setSmartProviderLogLevel('silent'); + onchainHookType = await hook.hookType(); + + switch (onchainHookType) { + case OnchainHookType.ROUTING: + return this.deriveDomainRoutingConfig(address); + case OnchainHookType.AGGREGATION: + return this.deriveAggregationConfig(address); + case OnchainHookType.MERKLE_TREE: + return this.deriveMerkleTreeConfig(address); + case OnchainHookType.INTERCHAIN_GAS_PAYMASTER: + return this.deriveIgpConfig(address); + case OnchainHookType.FALLBACK_ROUTING: + return this.deriveFallbackRoutingConfig(address); + case OnchainHookType.PAUSABLE: + return this.derivePausableConfig(address); + case OnchainHookType.PROTOCOL_FEE: + return this.deriveProtocolFeeConfig(address); + // ID_AUTH_ISM could be OPStackHook, ERC5164Hook or LayerZeroV2Hook + // For now assume it's OP_STACK + case OnchainHookType.ID_AUTH_ISM: + return this.deriveOpStackConfig(address); + default: + throw new Error( + `Unsupported HookType: ${OnchainHookType[onchainHookType]}`, + ); + } + } catch (e) { + this.logger.debug( + `Failed to derive ${onchainHookType} hook (${address}): ${e}`, + ); + } finally { + this.setSmartProviderLogLevel(getLogLevel()); // returns to original level defined by rootLogger } + return undefined; } async deriveMerkleTreeConfig( @@ -134,10 +153,14 @@ export class EvmHookReader implements HookReader { assert((await hook.hookType()) === OnchainHookType.AGGREGATION); const hooks = await hook.hooks(ethers.constants.AddressZero); - const hookConfigs = await concurrentMap( + const hookConfigs: DerivedHookConfig[] = await concurrentMap( this.concurrency, hooks, - async (hook) => this.deriveHookConfig(hook), + async (hook) => { + const hookConfig = await this.deriveHookConfig(hook); + assert(hookConfig, `No hook config found for ${hook}.`); + return hookConfig; + }, ); return { @@ -295,6 +318,10 @@ export class EvmHookReader implements HookReader { const fallbackHook = await hook.fallbackHook(); const fallbackHookConfig = await this.deriveHookConfig(fallbackHook); + assert( + fallbackHookConfig, + `No fallback hook config found for ${fallbackHook}.`, + ); return { owner, @@ -316,7 +343,9 @@ export class EvmHookReader implements HookReader { try { const domainHook = await hook.hooks(domainId); if (domainHook !== ethers.constants.AddressZero) { - domainHooks[chainName] = await this.deriveHookConfig(domainHook); + const hookConfig = await this.deriveHookConfig(domainHook); + assert(hookConfig, `No hook config found for ${domainHook}.`); + domainHooks[chainName] = hookConfig; } } catch (error) { this.logger.debug( @@ -345,4 +374,16 @@ export class EvmHookReader implements HookReader { type: HookType.PAUSABLE, }; } + + /** + * Conditionally sets the log level for a smart provider. + * + * @param level - The log level to set, e.g. 'debug', 'info', 'warn', 'error'. + */ + protected setSmartProviderLogLevel(level: string) { + if ('setLogLevel' in this.provider) { + //@ts-ignore + this.provider.setLogLevel(level); + } + } } From 2de63aab28c9c67fe2f0afc144b601110232884f Mon Sep 17 00:00:00 2001 From: Paul Balaji Date: Wed, 26 Jun 2024 15:09:27 +0100 Subject: [PATCH 63/73] fix(cli): add hook/ism commands (#4060) Forgot to add the new commands to the top-level cli.ts when I added back the commands in https://github.com/hyperlane-xyz/hyperlane-monorepo/pull/3990 Noticed when trying to test https://github.com/hyperlane-xyz/hyperlane-monorepo/pull/4055 Tested with: ```sh $ yarn workspace @hyperlane-xyz/cli run hyperlane ism read --chain inevm --address 0x79A7c7Fe443971CBc6baD623Fdf8019C379a7178 Hyperlane CLI {"level":30,"time":1719408936852,"pid":74170,"msg":"Your CLI version: 3.15.0, latest version: 3.16.0"} Hyperlane ISM Read ------------------ ISM Config at 0x79A7c7Fe443971CBc6baD623Fdf8019C379a7178 on inevm: address: "0x79A7c7Fe443971CBc6baD623Fdf8019C379a7178" type: staticAggregationIsm modules: - address: "0x718901b7570c59241304758345aF297Ff47D83A1" type: messageIdMultisigIsm validators: - "0x5450447aeE7B544c462C9352bEF7cAD049B0C2Dc" - "0x570AF9B7B36568C8877eeBBA6c6727aA9DAB7268" - "0x8292B1A53907eCE0f76Af8A50724e9492bcDc8A3" - "0xEAF5cF9100f36A4BaEeA779F8745DDa86159103C" threshold: 2 - address: "0x8dac7Df615Cb7599aCfD4EC2CBd9c67Cd5DF6BF6" type: merkleRootMultisigIsm validators: - "0x5450447aeE7B544c462C9352bEF7cAD049B0C2Dc" - "0x570AF9B7B36568C8877eeBBA6c6727aA9DAB7268" - "0x8292B1A53907eCE0f76Af8A50724e9492bcDc8A3" - "0xEAF5cF9100f36A4BaEeA779F8745DDa86159103C" threshold: 2 threshold: 1 ``` ```sh $ yarn workspace @hyperlane-xyz/cli run hyperlane hook read --chain polygon --address 0x0071740Bf129b05C4684abfbBeD248D80971cce2 Hyperlane CLI {"level":30,"time":1719409044431,"pid":75236,"msg":"Your CLI version: 3.15.0, latest version: 3.16.0"} Hyperlane Hook Read ------------------ Hook Config at 0x0071740Bf129b05C4684abfbBeD248D80971cce2 on polygon: owner: "0xa7ECcdb9Be08178f896c26b7BbD8C3D4E844d9Ba" address: "0x0071740Bf129b05C4684abfbBeD248D80971cce2" type: interchainGasPaymaster beneficiary: "0xa7ECcdb9Be08178f896c26b7BbD8C3D4E844d9Ba" oracleKey: "0xa7ECcdb9Be08178f896c26b7BbD8C3D4E844d9Ba" overhead: ancient8: 159736 blast: 159736 ethereum: 166887 bsc: 160545 inevm: 159736 gnosis: 160545 injective: 600000 celo: 160948 avalanche: 159736 fraxtal: 159337 arbitrum: 166887 base: 160948 sei: 159337 osmosis: 600000 scroll: 160545 mode: 159337 mantapacific: 182044 moonbeam: 160545 linea: 159736 neutron: 600000 polygonzkevm: 159736 redstone: 159337 optimism: 160948 zetachain: 159337 viction: 159736 oracleConfig: ancient8: tokenExchangeRate: "89752048061167" gasPrice: "483551859" blast: tokenExchangeRate: "89752048061167" gasPrice: "500000000" ethereum: tokenExchangeRate: "89752048061167" gasPrice: "20000000000" bsc: tokenExchangeRate: "15385035499726" gasPrice: "5000000000" inevm: tokenExchangeRate: "572910977607" gasPrice: "30301230683" gnosis: tokenExchangeRate: "27292763517" gasPrice: "633618644641" injective: tokenExchangeRate: "572910977607" gasPrice: "9777321413" celo: tokenExchangeRate: "15645849262" gasPrice: "1103178625684" avalanche: tokenExchangeRate: "656744948115" gasPrice: "43212830197" fraxtal: tokenExchangeRate: "89790551611140" gasPrice: "484265767" arbitrum: tokenExchangeRate: "89752048061167" gasPrice: "500000000" base: tokenExchangeRate: "89752048061167" gasPrice: "480773616" sei: tokenExchangeRate: "9438558163" gasPrice: "1842759845538" osmosis: tokenExchangeRate: "1448574549300000000" gasPrice: "3866" scroll: tokenExchangeRate: "89752048061167" gasPrice: "1445081549" mode: tokenExchangeRate: "89752048061167" gasPrice: "484473517" mantapacific: tokenExchangeRate: "89752048061167" gasPrice: "437064663" moonbeam: tokenExchangeRate: "5307373020" gasPrice: "3258335859000" linea: tokenExchangeRate: "89752048061167" gasPrice: "483551859" neutron: tokenExchangeRate: "1169238121200000000" gasPrice: "11976" polygonzkevm: tokenExchangeRate: "89752048061167" gasPrice: "3950000000" redstone: tokenExchangeRate: "89752048061167" gasPrice: "193789404" optimism: tokenExchangeRate: "89752048061167" gasPrice: "480773616" zetachain: tokenExchangeRate: "22360158382" gasPrice: "777856564583" viction: tokenExchangeRate: "9926843254" gasPrice: "1748784306278" ``` Signed-off-by: Paul Balaji <10051819+paulbalaji@users.noreply.github.com> --- typescript/cli/cli.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/typescript/cli/cli.ts b/typescript/cli/cli.ts index 17351e8ca..2328c2531 100644 --- a/typescript/cli/cli.ts +++ b/typescript/cli/cli.ts @@ -9,6 +9,8 @@ import { avsCommand } from './src/commands/avs.js'; import { configCommand } from './src/commands/config.js'; import { coreCommand } from './src/commands/core.js'; import { deployCommand } from './src/commands/deploy.js'; +import { hookCommand } from './src/commands/hook.js'; +import { ismCommand } from './src/commands/ism.js'; import { keyCommandOption, logFormatCommandOption, @@ -54,6 +56,8 @@ try { .command(configCommand) .command(coreCommand) .command(deployCommand) + .command(hookCommand) + .command(ismCommand) .command(registryCommand) .command(sendCommand) .command(statusCommand) From 44cc9bf6b33f5288462ecc027e9d624ae682d552 Mon Sep 17 00:00:00 2001 From: Mohammed Hussan Date: Wed, 26 Jun 2024 15:40:42 +0100 Subject: [PATCH 64/73] feat(cli): Add avs validator status command (#4056) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Description - Command to check all operators on our AVS and show the chains that they are validating on - Already been reviewed here https://github.com/hyperlane-xyz/hyperlane-monorepo/pull/4004, this PR was to merge it main instead of cli-2.0 Example usage: ``` yarn hyperlane avs check --chain ethereum --registry $REGISTRY Hyperlane CLI Checking AVS validator status for ethereum, this may take up to a minute to run... ❗️ MerkleTreeHook is not deployed on anvil8545 Operator name: Abacus Works AVS Operator Operator address: 0xFe114FcC7609578f525219a8eF77e2CCe27C5357 Validator address: 0x03c842db86A6A3E524D4a6615390c1Ea8E2b9541 Validating on... ethereum Storage location: s3://hyperlane-mainnet3-ethereum-validator-0/us-east-1 Latest merkle tree checkpoint index: 8219 Latest validator checkpoint index: 8219 ✅ Validator is signing latest checkpoint Operator name: Kelp by Kiln Operator address: 0x96fC0751e0febe7296d4625500f8e4535a002c7d Validator address: 0xEa5f21513182e97D0169a4d2E7aC71Ae8827F5bC Validating on... ethereum Storage location: s3://kiln-mainnet-hyperlane-validator-signatures/eu-west-1/ethereum Latest merkle tree checkpoint index: 8219 ❌ Failed to fetch latest signed checkpoint index The following warnings were encountered: ❗️ Failed to fetch latest signed checkpoint index of validator on ethereum, this is likely due to failing to read an S3 bucket ``` ### Drive-by changes ### Related issues - Fixes #3976 ### Backward compatibility No ### Testing Manual --- .changeset/brave-penguins-ring.md | 6 + .../avs/vendored/IDelegationManager.sol | 5 + typescript/cli/src/avs/check.ts | 449 ++++++++++++++++++ typescript/cli/src/avs/config.ts | 3 + typescript/cli/src/avs/stakeRegistry.ts | 2 +- typescript/cli/src/commands/avs.ts | 69 ++- typescript/cli/src/commands/options.ts | 17 + typescript/cli/src/logger.ts | 8 + typescript/cli/src/utils/files.ts | 8 + typescript/cli/src/validator/utils.ts | 69 +++ typescript/sdk/src/aws/s3.ts | 2 +- typescript/sdk/src/aws/validator.ts | 4 + 12 files changed, 627 insertions(+), 15 deletions(-) create mode 100644 .changeset/brave-penguins-ring.md create mode 100644 typescript/cli/src/avs/check.ts create mode 100644 typescript/cli/src/validator/utils.ts diff --git a/.changeset/brave-penguins-ring.md b/.changeset/brave-penguins-ring.md new file mode 100644 index 000000000..8c04ddbc6 --- /dev/null +++ b/.changeset/brave-penguins-ring.md @@ -0,0 +1,6 @@ +--- +'@hyperlane-xyz/cli': minor +'@hyperlane-xyz/core': minor +--- + +Add CLI command to support AVS validator status check diff --git a/solidity/contracts/interfaces/avs/vendored/IDelegationManager.sol b/solidity/contracts/interfaces/avs/vendored/IDelegationManager.sol index 8af9f453a..50fe9295a 100644 --- a/solidity/contracts/interfaces/avs/vendored/IDelegationManager.sol +++ b/solidity/contracts/interfaces/avs/vendored/IDelegationManager.sol @@ -20,6 +20,11 @@ interface IDelegationManager { uint32 stakerOptOutWindowBlocks; } + event OperatorMetadataURIUpdated( + address indexed operator, + string metadataURI + ); + function registerAsOperator( OperatorDetails calldata registeringOperatorDetails, string calldata metadataURI diff --git a/typescript/cli/src/avs/check.ts b/typescript/cli/src/avs/check.ts new file mode 100644 index 000000000..6730463e6 --- /dev/null +++ b/typescript/cli/src/avs/check.ts @@ -0,0 +1,449 @@ +import { Wallet } from 'ethers'; + +import { + ECDSAStakeRegistry__factory, + IDelegationManager__factory, + MerkleTreeHook__factory, + ValidatorAnnounce__factory, +} from '@hyperlane-xyz/core'; +import { ChainMap, ChainName, MultiProvider } from '@hyperlane-xyz/sdk'; +import { Address, ProtocolType, isObjEmpty } from '@hyperlane-xyz/utils'; + +import { CommandContext } from '../context/types.js'; +import { + errorRed, + log, + logBlue, + logBlueKeyValue, + logBoldBlue, + logDebug, + logGreen, + warnYellow, +} from '../logger.js'; +import { indentYamlOrJson } from '../utils/files.js'; +import { + getLatestMerkleTreeCheckpointIndex, + getLatestValidatorCheckpointIndexAndUrl, + getValidatorStorageLocations, + isValidatorSigningLatestCheckpoint, +} from '../validator/utils.js'; + +import { avsAddresses } from './config.js'; +import { readOperatorFromEncryptedJson } from './stakeRegistry.js'; + +interface ChainInfo { + storageLocation?: string; + latestMerkleTreeCheckpointIndex?: number; + latestValidatorCheckpointIndex?: number; + validatorSynced?: boolean; + warnings?: string[]; +} + +interface ValidatorInfo { + operatorAddress: Address; + operatorName?: string; + chains: ChainMap; +} + +export const checkValidatorAvsSetup = async ( + chain: string, + context: CommandContext, + operatorKeyPath?: string, + operatorAddress?: string, +) => { + logBlue( + `Checking AVS validator status for ${chain}, ${ + !operatorKeyPath ? 'this may take up to a minute to run' : '' + }...`, + ); + + const { multiProvider } = context; + + const topLevelErrors: string[] = []; + + let operatorWallet: Wallet | undefined; + if (operatorKeyPath) { + operatorWallet = await readOperatorFromEncryptedJson(operatorKeyPath); + } + + const avsOperatorRecord = await getAvsOperators( + chain, + multiProvider, + topLevelErrors, + operatorAddress ?? operatorWallet?.address, + ); + + await setOperatorName( + chain, + avsOperatorRecord, + multiProvider, + topLevelErrors, + ); + + if (!isObjEmpty(avsOperatorRecord)) { + await setValidatorInfo(context, avsOperatorRecord, topLevelErrors); + } + + logOutput(avsOperatorRecord, topLevelErrors); +}; + +const getAvsOperators = async ( + chain: string, + multiProvider: MultiProvider, + topLevelErrors: string[], + operatorKey?: string, +): Promise> => { + const avsOperators: Record = {}; + + const ecdsaStakeRegistryAddress = getEcdsaStakeRegistryAddress( + chain, + topLevelErrors, + ); + + if (!ecdsaStakeRegistryAddress) { + return avsOperators; + } + + const ecdsaStakeRegistry = ECDSAStakeRegistry__factory.connect( + ecdsaStakeRegistryAddress, + multiProvider.getProvider(chain), + ); + + if (operatorKey) { + // If operator key is provided, only fetch the operator's validator info + const signingKey = await ecdsaStakeRegistry.getLastestOperatorSigningKey( + operatorKey, + ); + avsOperators[signingKey] = { + operatorAddress: operatorKey, + chains: {}, + }; + + return avsOperators; + } + + const filter = ecdsaStakeRegistry.filters.SigningKeyUpdate(null, null); + const provider = multiProvider.getProvider(chain); + const latestBlock = await provider.getBlockNumber(); + const blockLimit = 50000; // 50k blocks per query + + let fromBlock = 1625972; // when ecdsaStakeRegistry was deployed + + while (fromBlock < latestBlock) { + const toBlock = Math.min(fromBlock + blockLimit, latestBlock); + const logs = await ecdsaStakeRegistry.queryFilter( + filter, + fromBlock, + toBlock, + ); + + logs.forEach((log) => { + const event = ecdsaStakeRegistry.interface.parseLog(log); + const operatorKey = event.args.operator; + const signingKey = event.args.newSigningKey; + + if (avsOperators[signingKey]) { + avsOperators[signingKey].operatorAddress = operatorKey; + } else { + avsOperators[signingKey] = { + operatorAddress: operatorKey, + chains: {}, + }; + } + }); + + fromBlock = toBlock + 1; + } + + return avsOperators; +}; + +const getAVSMetadataURI = async ( + chain: string, + operatorAddress: string, + multiProvider: MultiProvider, +): Promise => { + const delegationManagerAddress = avsAddresses[chain]['delegationManager']; + + const delegationManager = IDelegationManager__factory.connect( + delegationManagerAddress, + multiProvider.getProvider(chain), + ); + + const filter = delegationManager.filters.OperatorMetadataURIUpdated( + operatorAddress, + null, + ); + + const provider = multiProvider.getProvider(chain); + const latestBlock = await provider.getBlockNumber(); + const blockLimit = 50000; // 50k blocks per query + + let fromBlock = 17445563; + while (fromBlock < latestBlock) { + const toBlock = Math.min(fromBlock + blockLimit, latestBlock); + const logs = await delegationManager.queryFilter( + filter, + fromBlock, + toBlock, + ); + + if (logs.length > 0) { + const event = delegationManager.interface.parseLog(logs[0]); + return event.args.metadataURI; + } + + fromBlock = toBlock + 1; + } + + return undefined; +}; + +const setOperatorName = async ( + chain: string, + avsOperatorRecord: Record, + multiProvider: MultiProvider, + topLevelErrors: string[] = [], +) => { + for (const [_, validatorInfo] of Object.entries(avsOperatorRecord)) { + const metadataURI = await getAVSMetadataURI( + chain, + validatorInfo.operatorAddress, + multiProvider, + ); + + if (metadataURI) { + const operatorName = await fetchOperatorName(metadataURI); + if (operatorName) { + validatorInfo.operatorName = operatorName; + } else { + topLevelErrors.push( + `❗️ Failed to fetch operator name from metadataURI: ${metadataURI}`, + ); + } + } + } +}; + +const setValidatorInfo = async ( + context: CommandContext, + avsOperatorRecord: Record, + topLevelErrors: string[], +) => { + const { multiProvider, registry, chainMetadata } = context; + const failedToReadChains: string[] = []; + + const validatorAddresses = Object.keys(avsOperatorRecord); + + const chains = await registry.getChains(); + const addresses = await registry.getAddresses(); + + for (const chain of chains) { + // skip if chain is not an Ethereum chain + if (chainMetadata[chain].protocol !== ProtocolType.Ethereum) continue; + + const chainAddresses = addresses[chain]; + + // skip if no contract addresses are found for this chain + if (chainAddresses === undefined) continue; + + if (!chainAddresses.validatorAnnounce) { + topLevelErrors.push(`❗️ ValidatorAnnounce is not deployed on ${chain}`); + } + + if (!chainAddresses.merkleTreeHook) { + topLevelErrors.push(`❗️ MerkleTreeHook is not deployed on ${chain}`); + } + + if (!chainAddresses.validatorAnnounce || !chainAddresses.merkleTreeHook) { + continue; + } + + const validatorAnnounce = ValidatorAnnounce__factory.connect( + chainAddresses.validatorAnnounce, + multiProvider.getProvider(chain), + ); + + const merkleTreeHook = MerkleTreeHook__factory.connect( + chainAddresses.merkleTreeHook, + multiProvider.getProvider(chain), + ); + + const latestMerkleTreeCheckpointIndex = + await getLatestMerkleTreeCheckpointIndex(merkleTreeHook, chain); + + const validatorStorageLocations = await getValidatorStorageLocations( + validatorAnnounce, + validatorAddresses, + chain, + ); + + if (!validatorStorageLocations) { + failedToReadChains.push(chain); + continue; + } + + for (let i = 0; i < validatorAddresses.length; i++) { + const validatorAddress = validatorAddresses[i]; + const storageLocation = validatorStorageLocations[i]; + const warnings: string[] = []; + + // Skip if no storage location is found, address is not validating on this chain or if storage location string doesn't not start with s3:// + if ( + storageLocation.length === 0 || + !storageLocation[0].startsWith('s3://') + ) { + continue; + } + + const [latestValidatorCheckpointIndex, latestCheckpointUrl] = + (await getLatestValidatorCheckpointIndexAndUrl(storageLocation[0])) ?? [ + undefined, + undefined, + ]; + + if (!latestMerkleTreeCheckpointIndex) { + warnings.push( + `❗️ Failed to fetch latest checkpoint index of merkleTreeHook on ${chain}.`, + ); + } + + if (!latestValidatorCheckpointIndex) { + warnings.push( + `❗️ Failed to fetch latest signed checkpoint index of validator on ${chain}, this is likely due to failing to read an S3 bucket`, + ); + } + + let validatorSynced = undefined; + if (latestMerkleTreeCheckpointIndex && latestValidatorCheckpointIndex) { + validatorSynced = isValidatorSigningLatestCheckpoint( + latestValidatorCheckpointIndex, + latestMerkleTreeCheckpointIndex, + ); + } + + const chainInfo: ChainInfo = { + storageLocation: latestCheckpointUrl, + latestMerkleTreeCheckpointIndex, + latestValidatorCheckpointIndex, + validatorSynced, + warnings, + }; + + const validatorInfo = avsOperatorRecord[validatorAddress]; + if (validatorInfo) { + validatorInfo.chains[chain as ChainName] = chainInfo; + } + } + } + + if (failedToReadChains.length > 0) { + topLevelErrors.push( + `❗️ Failed to read storage locations onchain for ${failedToReadChains.join( + ', ', + )}`, + ); + } +}; + +const logOutput = ( + avsKeysRecord: Record, + topLevelErrors: string[], +) => { + if (topLevelErrors.length > 0) { + for (const error of topLevelErrors) { + errorRed(error); + } + } + + for (const [validatorAddress, data] of Object.entries(avsKeysRecord)) { + log('\n\n'); + if (data.operatorName) logBlueKeyValue('Operator name', data.operatorName); + logBlueKeyValue('Operator address', data.operatorAddress); + logBlueKeyValue('Validator address', validatorAddress); + + if (!isObjEmpty(data.chains)) { + logBoldBlue(indentYamlOrJson('Validating on...', 2)); + for (const [chain, chainInfo] of Object.entries(data.chains)) { + logBoldBlue(indentYamlOrJson(chain, 2)); + + if (chainInfo.storageLocation) { + logBlueKeyValue( + indentYamlOrJson('Storage location', 2), + chainInfo.storageLocation, + ); + } + + if (chainInfo.latestMerkleTreeCheckpointIndex) { + logBlueKeyValue( + indentYamlOrJson('Latest merkle tree checkpoint index', 2), + String(chainInfo.latestMerkleTreeCheckpointIndex), + ); + } + + if (chainInfo.latestValidatorCheckpointIndex) { + logBlueKeyValue( + indentYamlOrJson('Latest validator checkpoint index', 2), + String(chainInfo.latestValidatorCheckpointIndex), + ); + + if (chainInfo.validatorSynced) { + logGreen( + indentYamlOrJson('✅ Validator is signing latest checkpoint', 2), + ); + } else { + errorRed( + indentYamlOrJson( + '❌ Validator is not signing latest checkpoint', + 2, + ), + ); + } + } else { + errorRed( + indentYamlOrJson( + '❌ Failed to fetch latest signed checkpoint index', + 2, + ), + ); + } + + if (chainInfo.warnings && chainInfo.warnings.length > 0) { + warnYellow( + indentYamlOrJson('The following warnings were encountered:', 2), + ); + for (const warning of chainInfo.warnings) { + warnYellow(indentYamlOrJson(warning, 3)); + } + } + } + } else { + logBlue('Validator is not validating on any chain'); + } + } +}; + +const getEcdsaStakeRegistryAddress = ( + chain: string, + topLevelErrors: string[], +): Address | undefined => { + try { + return avsAddresses[chain]['ecdsaStakeRegistry']; + } catch (err) { + topLevelErrors.push( + `❗️ EcdsaStakeRegistry address not found for ${chain}`, + ); + return undefined; + } +}; + +const fetchOperatorName = async (metadataURI: string) => { + try { + const response = await fetch(metadataURI); + const data = await response.json(); + return data['name']; + } catch (err) { + logDebug(`Failed to fetch operator name from ${metadataURI}: ${err}`); + return undefined; + } +}; diff --git a/typescript/cli/src/avs/config.ts b/typescript/cli/src/avs/config.ts index 79715a676..72b4f0dfc 100644 --- a/typescript/cli/src/avs/config.ts +++ b/typescript/cli/src/avs/config.ts @@ -3,6 +3,7 @@ import { Address } from '@hyperlane-xyz/utils'; interface AVSContracts { avsDirectory: Address; + delegationManager: Address; proxyAdmin: Address; ecdsaStakeRegistry: Address; hyperlaneServiceManager: Address; @@ -12,12 +13,14 @@ interface AVSContracts { export const avsAddresses: ChainMap = { holesky: { avsDirectory: '0x055733000064333CaDDbC92763c58BF0192fFeBf', + delegationManager: '0xA44151489861Fe9e3055d95adC98FbD462B948e7', proxyAdmin: '0x33dB966328Ea213b0f76eF96CA368AB37779F065', ecdsaStakeRegistry: '0xFfa913705484C9BAea32Ffe9945BeA099A1DFF72', hyperlaneServiceManager: '0xc76E477437065093D353b7d56c81ff54D167B0Ab', }, ethereum: { avsDirectory: '0x135dda560e946695d6f155dacafc6f1f25c1f5af', + delegationManager: '0x39053D51B77DC0d36036Fc1fCc8Cb819df8Ef37A', proxyAdmin: '0x75EE15Ee1B4A75Fa3e2fDF5DF3253c25599cc659', ecdsaStakeRegistry: '0x272CF0BB70D3B4f79414E0823B426d2EaFd48910', hyperlaneServiceManager: '0xe8E59c6C8B56F2c178f63BCFC4ce5e5e2359c8fc', diff --git a/typescript/cli/src/avs/stakeRegistry.ts b/typescript/cli/src/avs/stakeRegistry.ts index d1bdd0716..705d52da1 100644 --- a/typescript/cli/src/avs/stakeRegistry.ts +++ b/typescript/cli/src/avs/stakeRegistry.ts @@ -109,7 +109,7 @@ export async function deregisterOperator({ ); } -async function readOperatorFromEncryptedJson( +export async function readOperatorFromEncryptedJson( operatorKeyPath: string, ): Promise { const encryptedJson = readFileAtPath(resolvePath(operatorKeyPath)); diff --git a/typescript/cli/src/commands/avs.ts b/typescript/cli/src/commands/avs.ts index ce238df62..66b1a9264 100644 --- a/typescript/cli/src/commands/avs.ts +++ b/typescript/cli/src/commands/avs.ts @@ -1,14 +1,21 @@ import { CommandModule, Options } from 'yargs'; import { ChainName } from '@hyperlane-xyz/sdk'; -import { Address } from '@hyperlane-xyz/utils'; +import { Address, ProtocolType } from '@hyperlane-xyz/utils'; +import { checkValidatorAvsSetup } from '../avs/check.js'; import { deregisterOperator, registerOperatorWithSignature, } from '../avs/stakeRegistry.js'; import { CommandModuleWithWriteContext } from '../context/types.js'; -import { log } from '../logger.js'; +import { errorRed, log } from '../logger.js'; + +import { + avsChainCommandOption, + demandOption, + operatorKeyPathCommandOption, +} from './options.js'; /** * Parent command @@ -20,6 +27,7 @@ export const avsCommand: CommandModule = { yargs .command(registerCommand) .command(deregisterCommand) + .command(checkCommand) .version(false) .demandCommand(), handler: () => log('Command required'), @@ -29,17 +37,8 @@ export const avsCommand: CommandModule = { * Registration command */ export const registrationOptions: { [k: string]: Options } = { - chain: { - type: 'string', - description: 'Chain to interact with the AVS on', - demandOption: true, - choices: ['holesky', 'ethereum'], - }, - operatorKeyPath: { - type: 'string', - description: 'Path to the operator key file', - demandOption: true, - }, + chain: avsChainCommandOption, + operatorKeyPath: demandOption(operatorKeyPathCommandOption), avsSigningKeyAddress: { type: 'string', description: 'Address of the AVS signing key', @@ -87,3 +86,47 @@ const deregisterCommand: CommandModuleWithWriteContext<{ process.exit(0); }, }; + +const checkCommand: CommandModuleWithWriteContext<{ + chain: ChainName; + operatorKeyPath?: string; + operatorAddress?: string; +}> = { + command: 'check', + describe: 'Check AVS', + builder: { + chain: avsChainCommandOption, + operatorKeyPath: operatorKeyPathCommandOption, + operatorAddress: { + type: 'string', + description: 'Address of the operator to check', + }, + }, + handler: async ({ context, chain, operatorKeyPath, operatorAddress }) => { + const { multiProvider } = context; + + // validate chain + if (!multiProvider.hasChain(chain)) { + errorRed( + `❌ No metadata found for ${chain}. Ensure it is included in your configured registry.`, + ); + process.exit(1); + } + + const chainMetadata = multiProvider.getChainMetadata(chain); + + if (chainMetadata.protocol !== ProtocolType.Ethereum) { + errorRed(`\n❌ Validator AVS check only supports EVM chains. Exiting.`); + process.exit(1); + } + + await checkValidatorAvsSetup( + chain, + context, + operatorKeyPath, + operatorAddress, + ); + + process.exit(0); + }, +}; diff --git a/typescript/cli/src/commands/options.ts b/typescript/cli/src/commands/options.ts index 3774b43cb..033fb7ba8 100644 --- a/typescript/cli/src/commands/options.ts +++ b/typescript/cli/src/commands/options.ts @@ -8,6 +8,11 @@ import { ENV } from '../utils/env.js'; /* Global options */ +export const demandOption = (option: Options): Options => ({ + ...option, + demandOption: true, +}); + export const logFormatCommandOption: Options = { type: 'string', description: 'Log output format', @@ -178,3 +183,15 @@ export const awsKeyIdCommandOption: Options = { type: 'string', describe: 'Key ID from AWS KMS', }; + +export const operatorKeyPathCommandOption: Options = { + type: 'string', + description: 'Path to the operator key file', +}; + +export const avsChainCommandOption: Options = { + type: 'string', + description: 'Chain to interact with the AVS on', + demandOption: true, + choices: ['holesky', 'ethereum'], +}; diff --git a/typescript/cli/src/logger.ts b/typescript/cli/src/logger.ts index b2a021d54..d5347c66d 100644 --- a/typescript/cli/src/logger.ts +++ b/typescript/cli/src/logger.ts @@ -35,6 +35,9 @@ export function logColor( } } export const logBlue = (...args: any) => logColor('info', chalk.blue, ...args); +export const logBlueKeyValue = (key: string, value: string) => { + logBlue(`${chalk.bold(`${key}:`)} ${value}`); +}; export const logPink = (...args: any) => logColor('info', chalk.magentaBright, ...args); export const logGray = (...args: any) => logColor('info', chalk.gray, ...args); @@ -43,11 +46,16 @@ export const logGreen = (...args: any) => export const logRed = (...args: any) => logColor('info', chalk.red, ...args); export const logBoldUnderlinedRed = (...args: any) => logColor('info', chalk.red.bold.underline, ...args); +export const logBoldBlue = (...args: any) => + logColor('info', chalk.blue.bold, ...args); export const logTip = (...args: any) => logColor('info', chalk.bgYellow, ...args); export const warnYellow = (...args: any) => logColor('warn', chalk.yellow, ...args); export const errorRed = (...args: any) => logColor('error', chalk.red, ...args); +export const logDebug = (msg: string, ...args: any) => + logger.debug(msg, ...args); + // No support for table in pino so print directly to console export const logTable = (...args: any) => console.table(...args); diff --git a/typescript/cli/src/utils/files.ts b/typescript/cli/src/utils/files.ts index 844eb4b79..277850af6 100644 --- a/typescript/cli/src/utils/files.ts +++ b/typescript/cli/src/utils/files.ts @@ -210,3 +210,11 @@ export async function runFileSelectionStep( if (filename) return filename; else throw new Error(`No filepath entered ${description}`); } + +export function indentYamlOrJson(str: string, indentLevel: number): string { + const indent = ' '.repeat(indentLevel); + return str + .split('\n') + .map((line) => indent + line) + .join('\n'); +} diff --git a/typescript/cli/src/validator/utils.ts b/typescript/cli/src/validator/utils.ts new file mode 100644 index 000000000..c96aa6612 --- /dev/null +++ b/typescript/cli/src/validator/utils.ts @@ -0,0 +1,69 @@ +import { MerkleTreeHook, ValidatorAnnounce } from '@hyperlane-xyz/core'; +import { S3Validator } from '@hyperlane-xyz/sdk'; + +import { logDebug } from '../logger.js'; + +export const getLatestMerkleTreeCheckpointIndex = async ( + merkleTreeHook: MerkleTreeHook, + chainName?: string, +): Promise => { + try { + const [_, latestCheckpointIndex] = await merkleTreeHook.latestCheckpoint(); + return latestCheckpointIndex; + } catch (err) { + const debugMessage = `Failed to get latest checkpoint index from merkleTreeHook contract ${ + chainName ? `on ${chainName}` : '' + } : ${err}`; + logDebug(debugMessage); + return undefined; + } +}; + +export const getValidatorStorageLocations = async ( + validatorAnnounce: ValidatorAnnounce, + validators: string[], + chainName?: string, +): Promise => { + try { + return await validatorAnnounce.getAnnouncedStorageLocations(validators); + } catch (err) { + const debugMessage = `Failed to get announced storage locations from validatorAnnounce contract ${ + chainName ? `on ${chainName}` : '' + } : ${err}`; + logDebug(debugMessage); + return undefined; + } +}; + +export const getLatestValidatorCheckpointIndexAndUrl = async ( + s3StorageLocation: string, +): Promise<[number, string] | undefined> => { + let s3Validator: S3Validator; + try { + s3Validator = await S3Validator.fromStorageLocation(s3StorageLocation); + } catch (err) { + logDebug( + `Failed to instantiate S3Validator at location ${s3StorageLocation}: ${err}`, + ); + return undefined; + } + try { + const latestCheckpointIndex = await s3Validator.getLatestCheckpointIndex(); + return [latestCheckpointIndex, s3Validator.getLatestCheckpointUrl()]; + } catch (err) { + logDebug( + `Failed to get latest checkpoint index from S3Validator at location ${s3StorageLocation}: ${err}`, + ); + return undefined; + } +}; + +export const isValidatorSigningLatestCheckpoint = ( + latestValidatorCheckpointIndex: number, + latestMerkleTreeCheckpointIndex: number, +): boolean => { + const diff = Math.abs( + latestValidatorCheckpointIndex - latestMerkleTreeCheckpointIndex, + ); + return diff < latestMerkleTreeCheckpointIndex / 100; +}; diff --git a/typescript/sdk/src/aws/s3.ts b/typescript/sdk/src/aws/s3.ts index a222617b3..462aa26fe 100644 --- a/typescript/sdk/src/aws/s3.ts +++ b/typescript/sdk/src/aws/s3.ts @@ -75,6 +75,6 @@ export class S3Wrapper { url(key: string): string { const Key = this.formatKey(key); - return `https://${this.config.bucket}.${this.config.region}.s3.amazonaws.com/${Key}`; + return `https://${this.config.bucket}.s3.${this.config.region}.amazonaws.com/${Key}`; } } diff --git a/typescript/sdk/src/aws/validator.ts b/typescript/sdk/src/aws/validator.ts index 11f91c576..3bb142feb 100644 --- a/typescript/sdk/src/aws/validator.ts +++ b/typescript/sdk/src/aws/validator.ts @@ -103,4 +103,8 @@ export class S3Validator extends BaseValidator { storageLocation(): string { return `${LOCATION_PREFIX}/${this.s3Bucket.config.bucket}/${this.s3Bucket.config.region}`; } + + getLatestCheckpointUrl(): string { + return this.s3Bucket.url(LATEST_KEY); + } } From 78b2eb8fee9be4afca7e535450d5a44506e0a907 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Noah=20Bayindirli=20=F0=9F=A5=82?= Date: Wed, 26 Jun 2024 19:57:19 +0100 Subject: [PATCH 65/73] update retreive ==> retrieve --- typescript/sdk/src/ism/EvmIsmModule.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typescript/sdk/src/ism/EvmIsmModule.ts b/typescript/sdk/src/ism/EvmIsmModule.ts index e9037d1a8..e75a12699 100644 --- a/typescript/sdk/src/ism/EvmIsmModule.ts +++ b/typescript/sdk/src/ism/EvmIsmModule.ts @@ -201,7 +201,7 @@ export class EvmIsmModule extends HyperlaneModule< // if it's a fallback routing ISM, do a mailbox diff check and deploy a new ISM if needed if (targetConfig.type === IsmType.FALLBACK_ROUTING) { - // can only retreive mailbox address if current ISM type is also Fallback Routing + // can only retrieve mailbox address if current ISM type is also Fallback Routing const mailboxAddress = currentConfig.type === IsmType.FALLBACK_ROUTING ? await MailboxClient__factory.connect( From df6a180538335638ed28867a614ba1daf13e3019 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Noah=20Bayindirli=20=F0=9F=A5=82?= Date: Wed, 26 Jun 2024 20:43:17 +0100 Subject: [PATCH 66/73] add changeset --- .changeset/slimy-ways-hide.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/slimy-ways-hide.md diff --git a/.changeset/slimy-ways-hide.md b/.changeset/slimy-ways-hide.md new file mode 100644 index 000000000..94082b9e9 --- /dev/null +++ b/.changeset/slimy-ways-hide.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/cli': major +--- + +Release CLI v4.0.0. From 45dc4f99c90dccbce5c46e9e77a58b576f95f5a2 Mon Sep 17 00:00:00 2001 From: Paul Balaji <10051819+paulbalaji@users.noreply.github.com> Date: Wed, 26 Jun 2024 21:32:48 +0100 Subject: [PATCH 67/73] use clean rust cache for e2e --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 868baf3a6..726e9f30e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -215,7 +215,7 @@ jobs: - name: rust cache uses: Swatinem/rust-cache@v2 with: - prefix-key: "v1-${{ runner.os }}-rust-cache" + prefix-key: "v2-${{ runner.os }}-rust-cache" shared-key: ${{ matrix.e2e-type }} workspaces: | ./rust From cf253328a07f1e0da340afcbd0edead43aa48c9e Mon Sep 17 00:00:00 2001 From: Paul Balaji Date: Wed, 26 Jun 2024 22:17:16 +0100 Subject: [PATCH 68/73] Update e2e-non-cosmwasm ci timeout --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 726e9f30e..a7d08626c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -266,7 +266,7 @@ jobs: working-directory: ./rust env: E2E_CI_MODE: 'true' - E2E_CI_TIMEOUT_SEC: '600' + E2E_CI_TIMEOUT_SEC: '900' E2E_KATHY_MESSAGES: '20' RUST_BACKTRACE: 'full' From a23cf6420cf4d84cc225234e3f3e5261c1329f5a Mon Sep 17 00:00:00 2001 From: Paul Balaji <10051819+paulbalaji@users.noreply.github.com> Date: Wed, 26 Jun 2024 23:01:44 +0100 Subject: [PATCH 69/73] tidy up test.yml branch triggers Signed-off-by: Paul Balaji <10051819+paulbalaji@users.noreply.github.com> --- .github/workflows/test.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a7d08626c..cd5f5dbbb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,9 +1,9 @@ name: test on: - # Triggers the workflow on pushes to main & cli-2.0 branches + # Triggers the workflow on pushes to main branch push: - branches: [main, cli-2.0] + branches: [main] paths-ignore: - '*.md' # Triggers on pull requests ignoring md files @@ -282,7 +282,7 @@ jobs: prebuild-cli-e2e: runs-on: larger-runner - if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.base_ref == 'main' || github.base_ref == 'cli-2.0') || github.event_name == 'merge_group' + if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.base_ref == 'main') || github.event_name == 'merge_group' steps: - uses: actions/checkout@v4 with: @@ -331,7 +331,7 @@ jobs: cli-advanced-e2e: runs-on: larger-runner - if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.base_ref == 'main' || github.base_ref == 'cli-2.0') || github.event_name == 'merge_group' + if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.base_ref == 'main') || github.event_name == 'merge_group' needs: [yarn-build, prebuild-cli-e2e] strategy: matrix: From d51813a1da224626a63e42507299a895f9f0dcdd Mon Sep 17 00:00:00 2001 From: Paul Balaji <10051819+paulbalaji@users.noreply.github.com> Date: Wed, 26 Jun 2024 23:56:28 +0100 Subject: [PATCH 70/73] bump E2E_CI_TIMEOUT_SEC on non-cosmwasm test to 1hr Signed-off-by: Paul Balaji <10051819+paulbalaji@users.noreply.github.com> --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cd5f5dbbb..19488daf0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -266,7 +266,7 @@ jobs: working-directory: ./rust env: E2E_CI_MODE: 'true' - E2E_CI_TIMEOUT_SEC: '900' + E2E_CI_TIMEOUT_SEC: '3600' E2E_KATHY_MESSAGES: '20' RUST_BACKTRACE: 'full' From 653bbcff52782a83dbcf6afe30b5a70c6b34a3f0 Mon Sep 17 00:00:00 2001 From: Paul Balaji Date: Thu, 27 Jun 2024 14:16:03 +0100 Subject: [PATCH 71/73] fix(e2e): hardcode kathy chains for e2e in send-test-messages.ts (#4067) resolves https://github.com/hyperlane-xyz/issues/issues/1291 - remove test4 from kathy test - drive-by tidy-up of `test4` metadata - resets e2e test timeout back to original figure - reuse original e2e rust cache gonna keep the original commits on here as a trail of thought --------- Signed-off-by: Paul Balaji <10051819+paulbalaji@users.noreply.github.com> --- .github/workflows/test.yml | 10 +++++----- .../infra/scripts/send-test-messages.ts | 20 +++++++++++++++++-- typescript/sdk/src/consts/testChains.ts | 5 ----- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 19488daf0..ebd05df12 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -188,7 +188,7 @@ jobs: e2e-matrix: runs-on: larger-runner - if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.base_ref == 'main') || github.event_name == 'merge_group' + if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.base_ref == 'main' || github.base_ref == 'cli-2.0') || github.event_name == 'merge_group' needs: [yarn-build] strategy: matrix: @@ -215,7 +215,7 @@ jobs: - name: rust cache uses: Swatinem/rust-cache@v2 with: - prefix-key: "v2-${{ runner.os }}-rust-cache" + prefix-key: "v1-${{ runner.os }}-rust-cache" shared-key: ${{ matrix.e2e-type }} workspaces: | ./rust @@ -266,7 +266,7 @@ jobs: working-directory: ./rust env: E2E_CI_MODE: 'true' - E2E_CI_TIMEOUT_SEC: '3600' + E2E_CI_TIMEOUT_SEC: '600' E2E_KATHY_MESSAGES: '20' RUST_BACKTRACE: 'full' @@ -282,7 +282,7 @@ jobs: prebuild-cli-e2e: runs-on: larger-runner - if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.base_ref == 'main') || github.event_name == 'merge_group' + if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.base_ref == 'main' || github.base_ref == 'cli-2.0') || github.event_name == 'merge_group' steps: - uses: actions/checkout@v4 with: @@ -331,7 +331,7 @@ jobs: cli-advanced-e2e: runs-on: larger-runner - if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.base_ref == 'main') || github.event_name == 'merge_group' + if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.base_ref == 'main' || github.base_ref == 'cli-2.0') || github.event_name == 'merge_group' needs: [yarn-build, prebuild-cli-e2e] strategy: matrix: diff --git a/typescript/infra/scripts/send-test-messages.ts b/typescript/infra/scripts/send-test-messages.ts index f602f4b30..d02826398 100644 --- a/typescript/infra/scripts/send-test-messages.ts +++ b/typescript/infra/scripts/send-test-messages.ts @@ -1,3 +1,4 @@ +import { Provider } from '@ethersproject/providers'; import { Wallet } from 'ethers'; import fs from 'fs'; import yargs from 'yargs'; @@ -100,15 +101,28 @@ async function main() { const { timeout, defaultHook, requiredHook, mineforever } = args; let messages = args.messages; + // Limit the test chains to a subset of the known chains + // E2E in Rust only knows about test1, test2 and test3 + const kathyTestChains = [ + TestChainName.test1, + TestChainName.test2, + TestChainName.test3, + ]; + + // Create a multi-provider with a signer const signer = new Wallet(ANVIL_KEY); const multiProvider = MultiProvider.createTestMultiProvider({ signer }); + + // Get the provider for the first chain const provider = multiProvider.getProvider(TestChainName.test1); + // Create core from addresses const addresses = JSON.parse( fs.readFileSync('./config/environments/test/core/addresses.json', 'utf8'), ); const core = HyperlaneCore.fromAddressesMap(addresses, multiProvider); + // helper function to get a random element from a list const randomElement = (list: T[]) => list[Math.floor(Math.random() * list.length)]; @@ -121,9 +135,11 @@ async function main() { const run_forever = messages === 0; while (run_forever || messages-- > 0) { // Round robin origin chain - const local = core.chains()[messages % core.chains().length]; + const local = kathyTestChains[messages % kathyTestChains.length]; // Random remote chain - const remote: ChainName = randomElement(await core.remoteChains(local)); + const remote: ChainName = randomElement( + kathyTestChains.filter((c) => c !== local), + ); const remoteId = multiProvider.getDomainId(remote); const contracts = core.getContracts(local); const mailbox = contracts.mailbox; diff --git a/typescript/sdk/src/consts/testChains.ts b/typescript/sdk/src/consts/testChains.ts index 878de285a..a5e8b91ee 100644 --- a/typescript/sdk/src/consts/testChains.ts +++ b/typescript/sdk/src/consts/testChains.ts @@ -68,11 +68,6 @@ export const test3: ChainMetadata = { export const test4: ChainMetadata = { ...test1, - blocks: { - confirmations: 1, - estimateBlockTime: 3, - reorgPeriod: 2, - }, chainId: 31337, displayName: 'Test 4', domainId: 31337, From 526984cc3caead7f4b8c819082a1fa716ddfd353 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 27 Jun 2024 14:30:46 +0000 Subject: [PATCH 72/73] Version Packages (#4034) This PR was opened by the [Changesets release](https://github.com/changesets/action) GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated. # Releases ## @hyperlane-xyz/cli@4.0.0 ### Major Changes - df6a18053: Release CLI v4.0.0. ### Minor Changes - 44cc9bf6b: Add CLI command to support AVS validator status check - b05ae38ac: Gracefully handle RPC failures during warp send & fix deriving hook error that prevents warp and core test messages on the cli. - 9304fe241: Use metadata builders in message relaying - 6398aab72: Upgrade registry to 2.1.1 - 5c8ba0b85: Rename hyperlane config create chain -> hyperlane registry init. Rename all `configure` to `init` - cd419c98a: Add a validator preFlightCheck command verifying that the validator has been announced for a given chain - 35f869950: Add command to support creating agent configs - bf7ad09da: feat(cli): add `warp --symbol` flag - b0828b3d0: Reintroduce `ism read` and `hook read` commands - 129bd871d: Add chain displayName prompt with default - 4040db723: Fix createDefaultWarpIsmConfig to default to trusted relayer and fallback routing without prompts - 6db9fa9ad: Implement hyperlane warp deploy - bd3ca9195: Updates ci-test.sh to ci-advanced-test.sh. - b7003cf35: Add stdout.rows to pagesize calculation with DEFAULT_PAGE_SIZE ### Patch Changes - 3283eefd6: Removes default pattern for chain name when creating a new chain. - 4dd2651ee: Add xerc20 limit lookups to warp read - 6b63c5d82: Adds deployment support for IsmConfig within a WarpRouteConfig - Updated dependencies [b05ae38ac] - Updated dependencies [9304fe241] - Updated dependencies [bdcbe1d16] - Updated dependencies [6b63c5d82] - Updated dependencies [e38d31685] - Updated dependencies [e0f226806] - Updated dependencies [6db9fa9ad] - @hyperlane-xyz/sdk@4.0.0 - @hyperlane-xyz/utils@4.0.0 ## @hyperlane-xyz/core@4.0.0 ### Minor Changes - 44cc9bf6b: Add CLI command to support AVS validator status check ### Patch Changes - @hyperlane-xyz/utils@4.0.0 ## @hyperlane-xyz/helloworld@4.0.0 ### Minor Changes - 6398aab72: Upgrade registry to 2.1.1 - bf7ad09da: feat(cli): add `warp --symbol` flag ### Patch Changes - Updated dependencies [44cc9bf6b] - Updated dependencies [b05ae38ac] - Updated dependencies [9304fe241] - Updated dependencies [bdcbe1d16] - Updated dependencies [6b63c5d82] - Updated dependencies [e38d31685] - Updated dependencies [e0f226806] - Updated dependencies [6db9fa9ad] - @hyperlane-xyz/core@4.0.0 - @hyperlane-xyz/sdk@4.0.0 ## @hyperlane-xyz/sdk@4.0.0 ### Minor Changes - b05ae38ac: Gracefully handle RPC failures during warp send & fix deriving hook error that prevents warp and core test messages on the cli. - 9304fe241: Use metadata builders in message relaying - bdcbe1d16: Add EvmWarpModule with create() - e38d31685: Add logic to set smart provider log level to disable provider logs during Warp TokenType derive - e0f226806: - Enables creation of new Hooks through the `EvmHookModule`. - Introduces an `EvmModuleDeployer` to perform the barebones tasks of deploying contracts/proxies. - 6db9fa9ad: Implement hyperlane warp deploy ### Patch Changes - 6b63c5d82: Adds deployment support for IsmConfig within a WarpRouteConfig - Updated dependencies [44cc9bf6b] - @hyperlane-xyz/core@4.0.0 - @hyperlane-xyz/utils@4.0.0 ## @hyperlane-xyz/utils@4.0.0 ## @hyperlane-xyz/infra@4.0.0 ### Minor Changes - 6398aab72: Upgrade registry to 2.1.1 - bf7ad09da: feat(cli): add `warp --symbol` flag ### Patch Changes - Updated dependencies [b05ae38ac] - Updated dependencies [9304fe241] - Updated dependencies [6398aab72] - Updated dependencies [bdcbe1d16] - Updated dependencies [6b63c5d82] - Updated dependencies [bf7ad09da] - Updated dependencies [e38d31685] - Updated dependencies [e0f226806] - Updated dependencies [6db9fa9ad] - @hyperlane-xyz/sdk@4.0.0 - @hyperlane-xyz/helloworld@4.0.0 - @hyperlane-xyz/utils@4.0.0 ## @hyperlane-xyz/ccip-server@4.0.0 Co-authored-by: github-actions[bot] --- .changeset/brave-penguins-ring.md | 6 -- .changeset/bright-emus-double.md | 5 -- .changeset/calm-eels-care.md | 6 -- .changeset/clean-bees-repeat.md | 6 -- .changeset/clean-numbers-know.md | 7 -- .changeset/five-baboons-smoke.md | 5 -- .changeset/five-owls-learn.md | 5 -- .changeset/giant-lies-whisper.md | 5 -- .changeset/hip-melons-build.md | 5 -- .changeset/late-rings-attack.md | 6 -- .changeset/lovely-boxes-bow.md | 5 -- .changeset/mean-books-clean.md | 7 -- .changeset/new-taxis-fry.md | 5 -- .changeset/new-timers-applaud.md | 5 -- .changeset/sharp-geckos-wash.md | 5 -- .changeset/shiny-cups-help.md | 5 -- .changeset/shy-countries-heal.md | 6 -- .changeset/slimy-toys-argue.md | 6 -- .changeset/slimy-ways-hide.md | 5 -- .changeset/sour-squids-buy.md | 5 -- .changeset/wise-cobras-juggle.md | 5 -- solidity/CHANGELOG.md | 10 +++ solidity/package.json | 4 +- typescript/ccip-server/CHANGELOG.md | 2 + typescript/ccip-server/package.json | 2 +- typescript/cli/CHANGELOG.md | 38 +++++++++ typescript/cli/package.json | 6 +- typescript/cli/src/version.ts | 2 +- typescript/helloworld/CHANGELOG.md | 20 +++++ typescript/helloworld/package.json | 6 +- typescript/infra/CHANGELOG.md | 22 +++++ typescript/infra/package.json | 8 +- typescript/sdk/CHANGELOG.md | 19 +++++ typescript/sdk/package.json | 6 +- typescript/utils/CHANGELOG.md | 2 + typescript/utils/package.json | 2 +- yarn.lock | 124 ++++++++++++++-------------- 37 files changed, 193 insertions(+), 195 deletions(-) delete mode 100644 .changeset/brave-penguins-ring.md delete mode 100644 .changeset/bright-emus-double.md delete mode 100644 .changeset/calm-eels-care.md delete mode 100644 .changeset/clean-bees-repeat.md delete mode 100644 .changeset/clean-numbers-know.md delete mode 100644 .changeset/five-baboons-smoke.md delete mode 100644 .changeset/five-owls-learn.md delete mode 100644 .changeset/giant-lies-whisper.md delete mode 100644 .changeset/hip-melons-build.md delete mode 100644 .changeset/late-rings-attack.md delete mode 100644 .changeset/lovely-boxes-bow.md delete mode 100644 .changeset/mean-books-clean.md delete mode 100644 .changeset/new-taxis-fry.md delete mode 100644 .changeset/new-timers-applaud.md delete mode 100644 .changeset/sharp-geckos-wash.md delete mode 100644 .changeset/shiny-cups-help.md delete mode 100644 .changeset/shy-countries-heal.md delete mode 100644 .changeset/slimy-toys-argue.md delete mode 100644 .changeset/slimy-ways-hide.md delete mode 100644 .changeset/sour-squids-buy.md delete mode 100644 .changeset/wise-cobras-juggle.md diff --git a/.changeset/brave-penguins-ring.md b/.changeset/brave-penguins-ring.md deleted file mode 100644 index 8c04ddbc6..000000000 --- a/.changeset/brave-penguins-ring.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@hyperlane-xyz/cli': minor -'@hyperlane-xyz/core': minor ---- - -Add CLI command to support AVS validator status check diff --git a/.changeset/bright-emus-double.md b/.changeset/bright-emus-double.md deleted file mode 100644 index d3c72ef21..000000000 --- a/.changeset/bright-emus-double.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperlane-xyz/cli': patch ---- - -Removes default pattern for chain name when creating a new chain. diff --git a/.changeset/calm-eels-care.md b/.changeset/calm-eels-care.md deleted file mode 100644 index a7911cd16..000000000 --- a/.changeset/calm-eels-care.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@hyperlane-xyz/cli': minor -'@hyperlane-xyz/sdk': minor ---- - -Gracefully handle RPC failures during warp send & fix deriving hook error that prevents warp and core test messages on the cli. diff --git a/.changeset/clean-bees-repeat.md b/.changeset/clean-bees-repeat.md deleted file mode 100644 index 0371000fa..000000000 --- a/.changeset/clean-bees-repeat.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@hyperlane-xyz/cli': minor -'@hyperlane-xyz/sdk': minor ---- - -Use metadata builders in message relaying diff --git a/.changeset/clean-numbers-know.md b/.changeset/clean-numbers-know.md deleted file mode 100644 index b48a6d303..000000000 --- a/.changeset/clean-numbers-know.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@hyperlane-xyz/helloworld': minor -'@hyperlane-xyz/infra': minor -'@hyperlane-xyz/cli': minor ---- - -Upgrade registry to 2.1.1 diff --git a/.changeset/five-baboons-smoke.md b/.changeset/five-baboons-smoke.md deleted file mode 100644 index 3cebc87ac..000000000 --- a/.changeset/five-baboons-smoke.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperlane-xyz/sdk': minor ---- - -Add EvmWarpModule with create() diff --git a/.changeset/five-owls-learn.md b/.changeset/five-owls-learn.md deleted file mode 100644 index 222e2e65f..000000000 --- a/.changeset/five-owls-learn.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@hyperlane-xyz/cli": patch ---- - -Add xerc20 limit lookups to warp read diff --git a/.changeset/giant-lies-whisper.md b/.changeset/giant-lies-whisper.md deleted file mode 100644 index 374acdf49..000000000 --- a/.changeset/giant-lies-whisper.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperlane-xyz/cli': minor ---- - -Rename hyperlane config create chain -> hyperlane registry init. Rename all `configure` to `init` diff --git a/.changeset/hip-melons-build.md b/.changeset/hip-melons-build.md deleted file mode 100644 index c775deeb3..000000000 --- a/.changeset/hip-melons-build.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperlane-xyz/cli': minor ---- - -Add a validator preFlightCheck command verifying that the validator has been announced for a given chain diff --git a/.changeset/late-rings-attack.md b/.changeset/late-rings-attack.md deleted file mode 100644 index 613f32aa3..000000000 --- a/.changeset/late-rings-attack.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@hyperlane-xyz/cli': patch -'@hyperlane-xyz/sdk': patch ---- - -Adds deployment support for IsmConfig within a WarpRouteConfig diff --git a/.changeset/lovely-boxes-bow.md b/.changeset/lovely-boxes-bow.md deleted file mode 100644 index 38ee46ae5..000000000 --- a/.changeset/lovely-boxes-bow.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperlane-xyz/cli': minor ---- - -Add command to support creating agent configs diff --git a/.changeset/mean-books-clean.md b/.changeset/mean-books-clean.md deleted file mode 100644 index e41ee504d..000000000 --- a/.changeset/mean-books-clean.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"@hyperlane-xyz/cli": minor -"@hyperlane-xyz/helloworld": minor -"@hyperlane-xyz/infra": minor ---- - -feat(cli): add `warp --symbol` flag diff --git a/.changeset/new-taxis-fry.md b/.changeset/new-taxis-fry.md deleted file mode 100644 index 88882518f..000000000 --- a/.changeset/new-taxis-fry.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperlane-xyz/cli': minor ---- - -Reintroduce `ism read` and `hook read` commands diff --git a/.changeset/new-timers-applaud.md b/.changeset/new-timers-applaud.md deleted file mode 100644 index af8820eac..000000000 --- a/.changeset/new-timers-applaud.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperlane-xyz/cli': minor ---- - -Add chain displayName prompt with default diff --git a/.changeset/sharp-geckos-wash.md b/.changeset/sharp-geckos-wash.md deleted file mode 100644 index 88ee03c28..000000000 --- a/.changeset/sharp-geckos-wash.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperlane-xyz/cli': minor ---- - -Fix createDefaultWarpIsmConfig to default to trusted relayer and fallback routing without prompts diff --git a/.changeset/shiny-cups-help.md b/.changeset/shiny-cups-help.md deleted file mode 100644 index dad4ee692..000000000 --- a/.changeset/shiny-cups-help.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperlane-xyz/sdk': minor ---- - -Add logic to set smart provider log level to disable provider logs during Warp TokenType derive diff --git a/.changeset/shy-countries-heal.md b/.changeset/shy-countries-heal.md deleted file mode 100644 index 295eba47f..000000000 --- a/.changeset/shy-countries-heal.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@hyperlane-xyz/sdk': minor ---- - -- Enables creation of new Hooks through the `EvmHookModule`. -- Introduces an `EvmModuleDeployer` to perform the barebones tasks of deploying contracts/proxies. diff --git a/.changeset/slimy-toys-argue.md b/.changeset/slimy-toys-argue.md deleted file mode 100644 index 7b9f760bb..000000000 --- a/.changeset/slimy-toys-argue.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@hyperlane-xyz/cli': minor -'@hyperlane-xyz/sdk': minor ---- - -Implement hyperlane warp deploy diff --git a/.changeset/slimy-ways-hide.md b/.changeset/slimy-ways-hide.md deleted file mode 100644 index 94082b9e9..000000000 --- a/.changeset/slimy-ways-hide.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperlane-xyz/cli': major ---- - -Release CLI v4.0.0. diff --git a/.changeset/sour-squids-buy.md b/.changeset/sour-squids-buy.md deleted file mode 100644 index 6bd0ae43c..000000000 --- a/.changeset/sour-squids-buy.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperlane-xyz/cli': minor ---- - -Updates ci-test.sh to ci-advanced-test.sh. diff --git a/.changeset/wise-cobras-juggle.md b/.changeset/wise-cobras-juggle.md deleted file mode 100644 index 4c7bb1a4a..000000000 --- a/.changeset/wise-cobras-juggle.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperlane-xyz/cli': minor ---- - -Add stdout.rows to pagesize calculation with DEFAULT_PAGE_SIZE diff --git a/solidity/CHANGELOG.md b/solidity/CHANGELOG.md index f8089d59e..c0feda2a6 100644 --- a/solidity/CHANGELOG.md +++ b/solidity/CHANGELOG.md @@ -1,5 +1,15 @@ # @hyperlane-xyz/core +## 4.0.0 + +### Minor Changes + +- 44cc9bf6b: Add CLI command to support AVS validator status check + +### Patch Changes + +- @hyperlane-xyz/utils@4.0.0 + ## 3.16.0 ### Patch Changes diff --git a/solidity/package.json b/solidity/package.json index ee53a0c7c..347dcc663 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.16.0", + "version": "4.0.0", "dependencies": { "@eth-optimism/contracts": "^0.6.0", - "@hyperlane-xyz/utils": "3.16.0", + "@hyperlane-xyz/utils": "4.0.0", "@layerzerolabs/lz-evm-oapp-v2": "2.0.2", "@openzeppelin/contracts": "^4.9.3", "@openzeppelin/contracts-upgradeable": "^v4.9.3", diff --git a/typescript/ccip-server/CHANGELOG.md b/typescript/ccip-server/CHANGELOG.md index 19b944f34..edff9f9e5 100644 --- a/typescript/ccip-server/CHANGELOG.md +++ b/typescript/ccip-server/CHANGELOG.md @@ -1,5 +1,7 @@ # @hyperlane-xyz/ccip-server +## 4.0.0 + ## 3.16.0 ## 3.15.1 diff --git a/typescript/ccip-server/package.json b/typescript/ccip-server/package.json index 64929084b..4d92b4f63 100644 --- a/typescript/ccip-server/package.json +++ b/typescript/ccip-server/package.json @@ -1,6 +1,6 @@ { "name": "@hyperlane-xyz/ccip-server", - "version": "3.16.0", + "version": "4.0.0", "description": "CCIP server", "typings": "dist/index.d.ts", "typedocMain": "src/index.ts", diff --git a/typescript/cli/CHANGELOG.md b/typescript/cli/CHANGELOG.md index 1089a543b..67aba3f94 100644 --- a/typescript/cli/CHANGELOG.md +++ b/typescript/cli/CHANGELOG.md @@ -1,5 +1,43 @@ # @hyperlane-xyz/cli +## 4.0.0 + +### Major Changes + +- df6a18053: Release CLI v4.0.0. + +### Minor Changes + +- 44cc9bf6b: Add CLI command to support AVS validator status check +- b05ae38ac: Gracefully handle RPC failures during warp send & fix deriving hook error that prevents warp and core test messages on the cli. +- 9304fe241: Use metadata builders in message relaying +- 6398aab72: Upgrade registry to 2.1.1 +- 5c8ba0b85: Rename hyperlane config create chain -> hyperlane registry init. Rename all `configure` to `init` +- cd419c98a: Add a validator preFlightCheck command verifying that the validator has been announced for a given chain +- 35f869950: Add command to support creating agent configs +- bf7ad09da: feat(cli): add `warp --symbol` flag +- b0828b3d0: Reintroduce `ism read` and `hook read` commands +- 129bd871d: Add chain displayName prompt with default +- 4040db723: Fix createDefaultWarpIsmConfig to default to trusted relayer and fallback routing without prompts +- 6db9fa9ad: Implement hyperlane warp deploy +- bd3ca9195: Updates ci-test.sh to ci-advanced-test.sh. +- b7003cf35: Add stdout.rows to pagesize calculation with DEFAULT_PAGE_SIZE + +### Patch Changes + +- 3283eefd6: Removes default pattern for chain name when creating a new chain. +- 4dd2651ee: Add xerc20 limit lookups to warp read +- 6b63c5d82: Adds deployment support for IsmConfig within a WarpRouteConfig +- Updated dependencies [b05ae38ac] +- Updated dependencies [9304fe241] +- Updated dependencies [bdcbe1d16] +- Updated dependencies [6b63c5d82] +- Updated dependencies [e38d31685] +- Updated dependencies [e0f226806] +- Updated dependencies [6db9fa9ad] + - @hyperlane-xyz/sdk@4.0.0 + - @hyperlane-xyz/utils@4.0.0 + ## 3.16.0 ### Patch Changes diff --git a/typescript/cli/package.json b/typescript/cli/package.json index f8c07586a..bcd5d077b 100644 --- a/typescript/cli/package.json +++ b/typescript/cli/package.json @@ -1,13 +1,13 @@ { "name": "@hyperlane-xyz/cli", - "version": "3.16.0", + "version": "4.0.0", "description": "A command-line utility for common Hyperlane operations", "dependencies": { "@aws-sdk/client-kms": "^3.577.0", "@aws-sdk/client-s3": "^3.577.0", "@hyperlane-xyz/registry": "2.1.1", - "@hyperlane-xyz/sdk": "3.16.0", - "@hyperlane-xyz/utils": "3.16.0", + "@hyperlane-xyz/sdk": "4.0.0", + "@hyperlane-xyz/utils": "4.0.0", "@inquirer/prompts": "^3.0.0", "asn1.js": "^5.4.1", "bignumber.js": "^9.1.1", diff --git a/typescript/cli/src/version.ts b/typescript/cli/src/version.ts index d244ae529..afb51c559 100644 --- a/typescript/cli/src/version.ts +++ b/typescript/cli/src/version.ts @@ -1 +1 @@ -export const VERSION = '3.16.0'; +export const VERSION = '4.0.0'; diff --git a/typescript/helloworld/CHANGELOG.md b/typescript/helloworld/CHANGELOG.md index e4fa352eb..5666e1d0e 100644 --- a/typescript/helloworld/CHANGELOG.md +++ b/typescript/helloworld/CHANGELOG.md @@ -1,5 +1,25 @@ # @hyperlane-xyz/helloworld +## 4.0.0 + +### Minor Changes + +- 6398aab72: Upgrade registry to 2.1.1 +- bf7ad09da: feat(cli): add `warp --symbol` flag + +### Patch Changes + +- Updated dependencies [44cc9bf6b] +- Updated dependencies [b05ae38ac] +- Updated dependencies [9304fe241] +- Updated dependencies [bdcbe1d16] +- Updated dependencies [6b63c5d82] +- Updated dependencies [e38d31685] +- Updated dependencies [e0f226806] +- Updated dependencies [6db9fa9ad] + - @hyperlane-xyz/core@4.0.0 + - @hyperlane-xyz/sdk@4.0.0 + ## 3.16.0 ### Patch Changes diff --git a/typescript/helloworld/package.json b/typescript/helloworld/package.json index 7d8520976..33745c2ca 100644 --- a/typescript/helloworld/package.json +++ b/typescript/helloworld/package.json @@ -1,11 +1,11 @@ { "name": "@hyperlane-xyz/helloworld", "description": "A basic skeleton of an Hyperlane app", - "version": "3.16.0", + "version": "4.0.0", "dependencies": { - "@hyperlane-xyz/core": "3.16.0", + "@hyperlane-xyz/core": "4.0.0", "@hyperlane-xyz/registry": "2.1.1", - "@hyperlane-xyz/sdk": "3.16.0", + "@hyperlane-xyz/sdk": "4.0.0", "@openzeppelin/contracts-upgradeable": "^4.9.3", "ethers": "^5.7.2" }, diff --git a/typescript/infra/CHANGELOG.md b/typescript/infra/CHANGELOG.md index b21d74c4f..5a9ac5e9a 100644 --- a/typescript/infra/CHANGELOG.md +++ b/typescript/infra/CHANGELOG.md @@ -1,5 +1,27 @@ # @hyperlane-xyz/infra +## 4.0.0 + +### Minor Changes + +- 6398aab72: Upgrade registry to 2.1.1 +- bf7ad09da: feat(cli): add `warp --symbol` flag + +### Patch Changes + +- Updated dependencies [b05ae38ac] +- Updated dependencies [9304fe241] +- Updated dependencies [6398aab72] +- Updated dependencies [bdcbe1d16] +- Updated dependencies [6b63c5d82] +- Updated dependencies [bf7ad09da] +- Updated dependencies [e38d31685] +- Updated dependencies [e0f226806] +- Updated dependencies [6db9fa9ad] + - @hyperlane-xyz/sdk@4.0.0 + - @hyperlane-xyz/helloworld@4.0.0 + - @hyperlane-xyz/utils@4.0.0 + ## 3.16.0 ### Minor Changes diff --git a/typescript/infra/package.json b/typescript/infra/package.json index 3cc965bca..276604123 100644 --- a/typescript/infra/package.json +++ b/typescript/infra/package.json @@ -1,7 +1,7 @@ { "name": "@hyperlane-xyz/infra", "description": "Infrastructure utilities for the Hyperlane Network", - "version": "3.16.0", + "version": "4.0.0", "dependencies": { "@arbitrum/sdk": "^3.0.0", "@aws-sdk/client-iam": "^3.74.0", @@ -13,10 +13,10 @@ "@ethersproject/hardware-wallets": "^5.7.0", "@ethersproject/providers": "^5.7.2", "@google-cloud/secret-manager": "^5.5.0", - "@hyperlane-xyz/helloworld": "3.16.0", + "@hyperlane-xyz/helloworld": "4.0.0", "@hyperlane-xyz/registry": "2.1.1", - "@hyperlane-xyz/sdk": "3.16.0", - "@hyperlane-xyz/utils": "3.16.0", + "@hyperlane-xyz/sdk": "4.0.0", + "@hyperlane-xyz/utils": "4.0.0", "@nomiclabs/hardhat-etherscan": "^3.0.3", "@solana/web3.js": "^1.78.0", "asn1.js": "5.4.1", diff --git a/typescript/sdk/CHANGELOG.md b/typescript/sdk/CHANGELOG.md index 59a76d8e5..53ba50fa9 100644 --- a/typescript/sdk/CHANGELOG.md +++ b/typescript/sdk/CHANGELOG.md @@ -1,5 +1,24 @@ # @hyperlane-xyz/sdk +## 4.0.0 + +### Minor Changes + +- b05ae38ac: Gracefully handle RPC failures during warp send & fix deriving hook error that prevents warp and core test messages on the cli. +- 9304fe241: Use metadata builders in message relaying +- bdcbe1d16: Add EvmWarpModule with create() +- e38d31685: Add logic to set smart provider log level to disable provider logs during Warp TokenType derive +- e0f226806: - Enables creation of new Hooks through the `EvmHookModule`. + - Introduces an `EvmModuleDeployer` to perform the barebones tasks of deploying contracts/proxies. +- 6db9fa9ad: Implement hyperlane warp deploy + +### Patch Changes + +- 6b63c5d82: Adds deployment support for IsmConfig within a WarpRouteConfig +- Updated dependencies [44cc9bf6b] + - @hyperlane-xyz/core@4.0.0 + - @hyperlane-xyz/utils@4.0.0 + ## 3.16.0 ### Minor Changes diff --git a/typescript/sdk/package.json b/typescript/sdk/package.json index 326415d70..d29d7a689 100644 --- a/typescript/sdk/package.json +++ b/typescript/sdk/package.json @@ -1,13 +1,13 @@ { "name": "@hyperlane-xyz/sdk", "description": "The official SDK for the Hyperlane Network", - "version": "3.16.0", + "version": "4.0.0", "dependencies": { "@aws-sdk/client-s3": "^3.74.0", "@cosmjs/cosmwasm-stargate": "^0.31.3", "@cosmjs/stargate": "^0.31.3", - "@hyperlane-xyz/core": "3.16.0", - "@hyperlane-xyz/utils": "3.16.0", + "@hyperlane-xyz/core": "4.0.0", + "@hyperlane-xyz/utils": "4.0.0", "@safe-global/api-kit": "1.3.0", "@safe-global/protocol-kit": "1.3.0", "@solana/spl-token": "^0.3.8", diff --git a/typescript/utils/CHANGELOG.md b/typescript/utils/CHANGELOG.md index 62d15f378..f87fa8b08 100644 --- a/typescript/utils/CHANGELOG.md +++ b/typescript/utils/CHANGELOG.md @@ -1,5 +1,7 @@ # @hyperlane-xyz/utils +## 4.0.0 + ## 3.16.0 ## 3.15.1 diff --git a/typescript/utils/package.json b/typescript/utils/package.json index e924add19..8095dec62 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.16.0", + "version": "4.0.0", "dependencies": { "@cosmjs/encoding": "^0.31.3", "@solana/web3.js": "^1.78.0", diff --git a/yarn.lock b/yarn.lock index ccfb28ce5..80adf046c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5684,8 +5684,8 @@ __metadata: "@ethersproject/abi": "npm:*" "@ethersproject/providers": "npm:*" "@hyperlane-xyz/registry": "npm:2.1.1" - "@hyperlane-xyz/sdk": "npm:3.16.0" - "@hyperlane-xyz/utils": "npm:3.16.0" + "@hyperlane-xyz/sdk": "npm:4.0.0" + "@hyperlane-xyz/utils": "npm:4.0.0" "@inquirer/prompts": "npm:^3.0.0" "@types/mocha": "npm:^10.0.1" "@types/node": "npm:^18.14.5" @@ -5714,12 +5714,28 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/core@npm:3.16.0, @hyperlane-xyz/core@workspace:solidity": +"@hyperlane-xyz/core@npm:3.7.0": + version: 3.7.0 + resolution: "@hyperlane-xyz/core@npm:3.7.0" + dependencies: + "@eth-optimism/contracts": "npm:^0.6.0" + "@hyperlane-xyz/utils": "npm:3.7.0" + "@openzeppelin/contracts": "npm:^4.9.3" + "@openzeppelin/contracts-upgradeable": "npm:^v4.9.3" + peerDependencies: + "@ethersproject/abi": "*" + "@ethersproject/providers": "*" + "@types/sinon-chai": "*" + checksum: efa01d943dd5b67830bb7244291c8ba9849472e804dff589463de76d3c03e56bc8d62454b575a6621aa1b8b53cc0d1d3b752a83d34f4b328ecd85e1ff23230d5 + languageName: node + linkType: hard + +"@hyperlane-xyz/core@npm:4.0.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.16.0" + "@hyperlane-xyz/utils": "npm:4.0.0" "@layerzerolabs/lz-evm-oapp-v2": "npm:2.0.2" "@layerzerolabs/solidity-examples": "npm:^1.1.0" "@nomiclabs/hardhat-ethers": "npm:^2.2.3" @@ -5754,29 +5770,13 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/core@npm:3.7.0": - version: 3.7.0 - resolution: "@hyperlane-xyz/core@npm:3.7.0" - dependencies: - "@eth-optimism/contracts": "npm:^0.6.0" - "@hyperlane-xyz/utils": "npm:3.7.0" - "@openzeppelin/contracts": "npm:^4.9.3" - "@openzeppelin/contracts-upgradeable": "npm:^v4.9.3" - peerDependencies: - "@ethersproject/abi": "*" - "@ethersproject/providers": "*" - "@types/sinon-chai": "*" - checksum: efa01d943dd5b67830bb7244291c8ba9849472e804dff589463de76d3c03e56bc8d62454b575a6621aa1b8b53cc0d1d3b752a83d34f4b328ecd85e1ff23230d5 - languageName: node - linkType: hard - -"@hyperlane-xyz/helloworld@npm:3.16.0, @hyperlane-xyz/helloworld@workspace:typescript/helloworld": +"@hyperlane-xyz/helloworld@npm:4.0.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.16.0" + "@hyperlane-xyz/core": "npm:4.0.0" "@hyperlane-xyz/registry": "npm:2.1.1" - "@hyperlane-xyz/sdk": "npm:3.16.0" + "@hyperlane-xyz/sdk": "npm:4.0.0" "@nomiclabs/hardhat-ethers": "npm:^2.2.3" "@nomiclabs/hardhat-waffle": "npm:^2.0.6" "@openzeppelin/contracts-upgradeable": "npm:^4.9.3" @@ -5823,10 +5823,10 @@ __metadata: "@ethersproject/hardware-wallets": "npm:^5.7.0" "@ethersproject/providers": "npm:^5.7.2" "@google-cloud/secret-manager": "npm:^5.5.0" - "@hyperlane-xyz/helloworld": "npm:3.16.0" + "@hyperlane-xyz/helloworld": "npm:4.0.0" "@hyperlane-xyz/registry": "npm:2.1.1" - "@hyperlane-xyz/sdk": "npm:3.16.0" - "@hyperlane-xyz/utils": "npm:3.16.0" + "@hyperlane-xyz/sdk": "npm:4.0.0" + "@hyperlane-xyz/utils": "npm:4.0.0" "@nomiclabs/hardhat-ethers": "npm:^2.2.3" "@nomiclabs/hardhat-etherscan": "npm:^3.0.3" "@nomiclabs/hardhat-waffle": "npm:^2.0.6" @@ -5887,15 +5887,43 @@ __metadata: languageName: node linkType: hard -"@hyperlane-xyz/sdk@npm:3.16.0, @hyperlane-xyz/sdk@workspace:typescript/sdk": +"@hyperlane-xyz/sdk@npm:3.7.0": + version: 3.7.0 + resolution: "@hyperlane-xyz/sdk@npm:3.7.0" + dependencies: + "@cosmjs/cosmwasm-stargate": "npm:^0.31.3" + "@cosmjs/stargate": "npm:^0.31.3" + "@hyperlane-xyz/core": "npm:3.7.0" + "@hyperlane-xyz/utils": "npm:3.7.0" + "@solana/spl-token": "npm:^0.3.8" + "@solana/web3.js": "npm:^1.78.0" + "@types/coingecko-api": "npm:^1.0.10" + "@types/debug": "npm:^4.1.7" + "@wagmi/chains": "npm:^1.8.0" + bignumber.js: "npm:^9.1.1" + coingecko-api: "npm:^1.0.10" + cosmjs-types: "npm:^0.9.0" + cross-fetch: "npm:^3.1.5" + debug: "npm:^4.3.4" + ethers: "npm:^5.7.2" + viem: "npm:^1.20.0" + zod: "npm:^3.21.2" + peerDependencies: + "@ethersproject/abi": "*" + "@ethersproject/providers": "*" + checksum: b124a42f34502c4dad4127723d345158f592056d7e60e17d87c84bf81664ead20232ffaff66e6c21968dfd5693ba5122910fbcaa6b7db5b05fdd5d2051592835 + languageName: node + linkType: hard + +"@hyperlane-xyz/sdk@npm:4.0.0, @hyperlane-xyz/sdk@workspace:typescript/sdk": version: 0.0.0-use.local resolution: "@hyperlane-xyz/sdk@workspace:typescript/sdk" dependencies: "@aws-sdk/client-s3": "npm:^3.74.0" "@cosmjs/cosmwasm-stargate": "npm:^0.31.3" "@cosmjs/stargate": "npm:^0.31.3" - "@hyperlane-xyz/core": "npm:3.16.0" - "@hyperlane-xyz/utils": "npm:3.16.0" + "@hyperlane-xyz/core": "npm:4.0.0" + "@hyperlane-xyz/utils": "npm:4.0.0" "@nomiclabs/hardhat-ethers": "npm:^2.2.3" "@nomiclabs/hardhat-waffle": "npm:^2.0.6" "@safe-global/api-kit": "npm:1.3.0" @@ -5935,35 +5963,19 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/sdk@npm:3.7.0": +"@hyperlane-xyz/utils@npm:3.7.0": version: 3.7.0 - resolution: "@hyperlane-xyz/sdk@npm:3.7.0" + resolution: "@hyperlane-xyz/utils@npm:3.7.0" dependencies: - "@cosmjs/cosmwasm-stargate": "npm:^0.31.3" - "@cosmjs/stargate": "npm:^0.31.3" - "@hyperlane-xyz/core": "npm:3.7.0" - "@hyperlane-xyz/utils": "npm:3.7.0" - "@solana/spl-token": "npm:^0.3.8" + "@cosmjs/encoding": "npm:^0.31.3" "@solana/web3.js": "npm:^1.78.0" - "@types/coingecko-api": "npm:^1.0.10" - "@types/debug": "npm:^4.1.7" - "@wagmi/chains": "npm:^1.8.0" bignumber.js: "npm:^9.1.1" - coingecko-api: "npm:^1.0.10" - cosmjs-types: "npm:^0.9.0" - cross-fetch: "npm:^3.1.5" - debug: "npm:^4.3.4" ethers: "npm:^5.7.2" - viem: "npm:^1.20.0" - zod: "npm:^3.21.2" - peerDependencies: - "@ethersproject/abi": "*" - "@ethersproject/providers": "*" - checksum: b124a42f34502c4dad4127723d345158f592056d7e60e17d87c84bf81664ead20232ffaff66e6c21968dfd5693ba5122910fbcaa6b7db5b05fdd5d2051592835 + checksum: c76f36913c572702b9dfe22fd868db6fed01c0da9485319e33e8d00a6b8a1bfdcecb5f61c8a3fd8ccbef0b36809e8055db62d75d0c6759d5e079ee330586bcd1 languageName: node linkType: hard -"@hyperlane-xyz/utils@npm:3.16.0, @hyperlane-xyz/utils@workspace:typescript/utils": +"@hyperlane-xyz/utils@npm:4.0.0, @hyperlane-xyz/utils@workspace:typescript/utils": version: 0.0.0-use.local resolution: "@hyperlane-xyz/utils@workspace:typescript/utils" dependencies: @@ -5981,18 +5993,6 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/utils@npm:3.7.0": - version: 3.7.0 - resolution: "@hyperlane-xyz/utils@npm:3.7.0" - dependencies: - "@cosmjs/encoding": "npm:^0.31.3" - "@solana/web3.js": "npm:^1.78.0" - bignumber.js: "npm:^9.1.1" - ethers: "npm:^5.7.2" - checksum: c76f36913c572702b9dfe22fd868db6fed01c0da9485319e33e8d00a6b8a1bfdcecb5f61c8a3fd8ccbef0b36809e8055db62d75d0c6759d5e079ee330586bcd1 - languageName: node - linkType: hard - "@hyperlane-xyz/widgets@npm:3.7.0": version: 3.7.0 resolution: "@hyperlane-xyz/widgets@npm:3.7.0" From befc38daba7d8c44615f0fb12ea1a9993c2c9b67 Mon Sep 17 00:00:00 2001 From: Daniel Savu <23065004+daniel-savu@users.noreply.github.com> Date: Thu, 27 Jun 2024 15:33:19 +0100 Subject: [PATCH 73/73] fix: agent log verbosity (#4054) ### Description tbh I don't know why, but this makes all the logs we were previously missing show up in e2e: - https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/b99920eccae22de9fc9d735af87cf0e2b306655a/rust/hyperlane-base/src/contract_sync/mod.rs#L155 - https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/f68319f46e6c140088e53c4a018bb23f12a8d8bd/rust/hyperlane-base/src/db/rocks/hyperlane_db.rs#L93 ### Drive-by changes ### Related issues - Fixes https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/3915 ### Backward compatibility ### Testing E2E, by counting occurrences of the two logs linked above in relayer log output. Before this change, those logs would be missing completely --------- Co-authored-by: Paul Balaji --- rust/hyperlane-base/src/settings/trace/mod.rs | 8 +++-- rust/utils/run-locally/src/invariants.rs | 30 +++++++++++++++---- rust/utils/run-locally/src/utils.rs | 24 ++++++++------- 3 files changed, 43 insertions(+), 19 deletions(-) diff --git a/rust/hyperlane-base/src/settings/trace/mod.rs b/rust/hyperlane-base/src/settings/trace/mod.rs index 00d9cb4c5..21999d159 100644 --- a/rust/hyperlane-base/src/settings/trace/mod.rs +++ b/rust/hyperlane-base/src/settings/trace/mod.rs @@ -66,12 +66,12 @@ impl TracingConfig { if self.level < Level::DependencyTrace { // Reduce log noise from trusted libraries that we can reasonably assume are working correctly target_layer = target_layer - .with_target("hyper", Level::Info) + .with_target("hyper::", Level::Info) .with_target("rusoto_core", Level::Info) .with_target("rustls", Level::Info) .with_target("reqwest", Level::Info) .with_target("runtime", Level::Debug) - .with_target("h2", Level::Info) + .with_target("h2::", Level::Info) .with_target("tower", Level::Info) .with_target("tendermint", Level::Info) .with_target("tokio", Level::Debug) @@ -81,7 +81,9 @@ impl TracingConfig { if self.level < Level::Trace { // only show sqlx query logs at trace level - target_layer = target_layer.with_target("sqlx::query", Level::Warn); + target_layer = target_layer + .with_target("sqlx::query", Level::Warn) + .with_target("hyper::", Level::Warn); } let fmt_layer: LogOutputLayer<_> = self.fmt.into(); let err_layer = tracing_error::ErrorLayer::default(); diff --git a/rust/utils/run-locally/src/invariants.rs b/rust/utils/run-locally/src/invariants.rs index 18bc026c2..91866415b 100644 --- a/rust/utils/run-locally/src/invariants.rs +++ b/rust/utils/run-locally/src/invariants.rs @@ -62,12 +62,17 @@ pub fn termination_invariants_met( .sum::(); let log_file_path = AGENT_LOGGING_DIR.join("RLY-output.log"); + const STORING_NEW_MESSAGE_LOG_MESSAGE: &str = "Storing new message in db"; + const LOOKING_FOR_EVENTS_LOG_MESSAGE: &str = "Looking for events in index range"; + const HYPER_INCOMING_BODY_LOG_MESSAGE: &str = "incoming body completed"; let relayer_logfile = File::open(log_file_path)?; - let gas_expenditure_log_count = - get_matching_lines(&relayer_logfile, GAS_EXPENDITURE_LOG_MESSAGE) - .unwrap() - .len(); - + let invariant_logs = &[ + STORING_NEW_MESSAGE_LOG_MESSAGE, + LOOKING_FOR_EVENTS_LOG_MESSAGE, + GAS_EXPENDITURE_LOG_MESSAGE, + HYPER_INCOMING_BODY_LOG_MESSAGE, + ]; + let log_counts = get_matching_lines(&relayer_logfile, invariant_logs); // Zero insertion messages don't reach `submit` stage where gas is spent, so we only expect these logs for the other messages. // TODO: Sometimes we find more logs than expected. This may either mean that gas is deducted twice for the same message due to a bug, // or that submitting the message transaction fails for some messages. Figure out which is the case and convert this check to @@ -76,9 +81,22 @@ pub fn termination_invariants_met( // (`Transaction attempting to process message either reverted or was reorged`) // in which case more gas expenditure logs than messages are expected. assert!( - gas_expenditure_log_count as u32 >= total_messages_expected, + log_counts.get(GAS_EXPENDITURE_LOG_MESSAGE).unwrap() >= &total_messages_expected, "Didn't record gas payment for all delivered messages" ); + // These tests check that we fixed https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/3915, where some logs would not show up + assert!( + log_counts.get(STORING_NEW_MESSAGE_LOG_MESSAGE).unwrap() > &0, + "Didn't find any logs about storing messages in db" + ); + assert!( + log_counts.get(LOOKING_FOR_EVENTS_LOG_MESSAGE).unwrap() > &0, + "Didn't find any logs about looking for events in index range" + ); + assert!( + log_counts.get(HYPER_INCOMING_BODY_LOG_MESSAGE).is_none(), + "Verbose logs not expected at the log level set in e2e" + ); let gas_payment_sealevel_events_count = fetch_metric( "9092", diff --git a/rust/utils/run-locally/src/utils.rs b/rust/utils/run-locally/src/utils.rs index 531970174..5e5dd6a12 100644 --- a/rust/utils/run-locally/src/utils.rs +++ b/rust/utils/run-locally/src/utils.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use std::fs::File; use std::io::{self, BufRead}; use std::path::{Path, PathBuf}; @@ -118,15 +119,18 @@ pub fn stop_child(child: &mut Child) { }; } -pub fn get_matching_lines(file: &File, search_string: &str) -> io::Result> { +pub fn get_matching_lines(file: &File, search_strings: &[&str]) -> HashMap { let reader = io::BufReader::new(file); - - // Read lines and collect those that contain the search string - let matching_lines: Vec = reader - .lines() - .map_while(Result::ok) - .filter(|line| line.contains(search_string)) - .collect(); - - Ok(matching_lines) + let mut matches = HashMap::new(); + + let mut lines = reader.lines(); + while let Some(Ok(line)) = lines.next() { + search_strings.iter().for_each(|search_string| { + if line.contains(search_string) { + let count = matches.entry(search_string.to_string()).or_insert(0); + *count += 1; + } + }); + } + matches }