diff --git a/.changeset/many-llamas-count.md b/.changeset/many-llamas-count.md deleted file mode 100644 index 1353d335d1..0000000000 --- a/.changeset/many-llamas-count.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperlane-xyz/infra': minor ---- - -updated zero ETH warp route config getter diff --git a/.changeset/proud-waves-camp.md b/.changeset/proud-waves-camp.md deleted file mode 100644 index a7cab11392..0000000000 --- a/.changeset/proud-waves-camp.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperlane-xyz/cli': minor ---- - -Fix issue where warp deploy artifacts did not include correct symbols. diff --git a/.changeset/quick-walls-deny.md b/.changeset/quick-walls-deny.md deleted file mode 100644 index accf9d3383..0000000000 --- a/.changeset/quick-walls-deny.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperlane-xyz/sdk': minor ---- - -Deploy to trumpchain. diff --git a/.changeset/thirty-apricots-eat.md b/.changeset/thirty-apricots-eat.md deleted file mode 100644 index 2d48e803cc..0000000000 --- a/.changeset/thirty-apricots-eat.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperlane-xyz/infra': minor ---- - -Add support for Artela config diff --git a/.changeset/weak-dingos-thank.md b/.changeset/weak-dingos-thank.md deleted file mode 100644 index ab230544bd..0000000000 --- a/.changeset/weak-dingos-thank.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperlane-xyz/sdk': minor ---- - -Deploy to flametestnet, sonicblaze. Remove support for sonictestnet. diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 12f8c90f4a..aefcb0d9a9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -135,10 +135,12 @@ jobs: # Other commands - relay # Warp Commands - - warp-init - - warp-read - warp-apply + - warp-check - warp-deploy + - warp-init + - warp-read + - warp-send steps: - uses: actions/checkout@v4 with: diff --git a/.registryrc b/.registryrc index 82c0369f03..341d982666 100644 --- a/.registryrc +++ b/.registryrc @@ -1 +1 @@ -80023dd9e9fadd4aa9fd868b5e7ad76dc3082494 +2b3f78f7692b6b62e0f85023a29ecc2b2ab85008 diff --git a/Dockerfile b/Dockerfile index 6f8e3939d4..111a9093c4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -30,9 +30,16 @@ COPY solidity ./solidity RUN yarn build +# Baked-in registry version +# keep for back-compat until we update all usage of the monorepo image (e.g. key-funder) ENV REGISTRY_URI="/hyperlane-registry" ARG REGISTRY_COMMIT="main" RUN git clone https://github.com/hyperlane-xyz/hyperlane-registry.git "$REGISTRY_URI" \ && cd "$REGISTRY_URI" \ && git fetch origin "$REGISTRY_COMMIT" \ && git checkout "$REGISTRY_COMMIT" + +# Add entrypoint script that allows overriding the registry commit +COPY docker-entrypoint.sh /usr/local/bin/ +RUN chmod +x /usr/local/bin/docker-entrypoint.sh +ENTRYPOINT ["docker-entrypoint.sh"] diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh new file mode 100644 index 0000000000..719396d5bd --- /dev/null +++ b/docker-entrypoint.sh @@ -0,0 +1,18 @@ +#!/bin/sh +set -e + +# Set default registry URI, same as Dockerfile +REGISTRY_URI="/hyperlane-registry" + +# Only update registry if REGISTRY_COMMIT is set +if [ -n "$REGISTRY_COMMIT" ]; then + echo "Updating Hyperlane registry to commit: ${REGISTRY_COMMIT}" + OLDPWD=$(pwd) + cd "$REGISTRY_URI" + git fetch origin "$REGISTRY_COMMIT" + git checkout "$REGISTRY_COMMIT" + cd "$OLDPWD" +fi + +# Execute the main container command +exec "$@" diff --git a/rust/main/agents/scraper/migration/src/m20230309_000003_create_table_cursor.rs b/rust/main/agents/scraper/migration/src/m20230309_000003_create_table_cursor.rs index dd8c1d2e96..f3fd3c331b 100644 --- a/rust/main/agents/scraper/migration/src/m20230309_000003_create_table_cursor.rs +++ b/rust/main/agents/scraper/migration/src/m20230309_000003_create_table_cursor.rs @@ -36,7 +36,6 @@ impl MigrationTrait for Migration { .to_owned(), ) .await?; - manager .create_index( Index::create() @@ -47,7 +46,17 @@ impl MigrationTrait for Migration { .to_owned(), ) .await?; - + manager + .create_index( + Index::create() + .table(Cursor::Table) + .name("cursor_domain_height_idx") + .col(Cursor::Domain) + .col(Cursor::Height) + .index_type(IndexType::BTree) + .to_owned(), + ) + .await?; Ok(()) } diff --git a/rust/main/agents/scraper/migration/src/m20230309_000004_create_table_delivered_message.rs b/rust/main/agents/scraper/migration/src/m20230309_000004_create_table_delivered_message.rs index 45cb71ec47..e30663fb10 100644 --- a/rust/main/agents/scraper/migration/src/m20230309_000004_create_table_delivered_message.rs +++ b/rust/main/agents/scraper/migration/src/m20230309_000004_create_table_delivered_message.rs @@ -61,6 +61,29 @@ impl MigrationTrait for Migration { .to_owned(), ) .await?; + manager + .create_index( + Index::create() + .table(DeliveredMessage::Table) + .name("delivered_message_domain_destination_mailbox_idx") + .col(DeliveredMessage::Domain) + .col(DeliveredMessage::DestinationMailbox) + .index_type(IndexType::BTree) + .to_owned(), + ) + .await?; + manager + .create_index( + Index::create() + .table(DeliveredMessage::Table) + .name("delivered_message_domain_destination_mailbox_sequence_idx") + .col(DeliveredMessage::Domain) + .col(DeliveredMessage::DestinationMailbox) + .col(DeliveredMessage::Sequence) + .index_type(IndexType::BTree) + .to_owned(), + ) + .await?; manager .create_index( Index::create() diff --git a/rust/main/agents/scraper/migration/src/m20230309_000004_create_table_gas_payment.rs b/rust/main/agents/scraper/migration/src/m20230309_000004_create_table_gas_payment.rs index 9285c83468..8bafbbdb05 100644 --- a/rust/main/agents/scraper/migration/src/m20230309_000004_create_table_gas_payment.rs +++ b/rust/main/agents/scraper/migration/src/m20230309_000004_create_table_gas_payment.rs @@ -86,7 +86,28 @@ impl MigrationTrait for Migration { .to_owned(), ) .await?; - + manager + .create_index( + Index::create() + .table(GasPayment::Table) + .name("gas_payment_domain_id_idx") + .col(GasPayment::Domain) + .col(GasPayment::Id) + .index_type(IndexType::BTree) + .to_owned(), + ) + .await?; + manager + .create_index( + Index::create() + .table(GasPayment::Table) + .name("gas_payment_origin_id_idx") + .col(GasPayment::Origin) + .col(GasPayment::Id) + .index_type(IndexType::BTree) + .to_owned(), + ) + .await?; manager .create_index( Index::create() @@ -99,7 +120,6 @@ impl MigrationTrait for Migration { .to_owned(), ) .await?; - manager .get_connection() .execute_unprepared(&format!( @@ -124,7 +144,6 @@ impl MigrationTrait for Migration { tgp_gas_amount = TotalGasPayment::TotalGasAmount.to_string(), )) .await?; - Ok(()) } diff --git a/rust/sealevel/environments/mainnet3/warp-routes/TONY-base-solanamainnet/program-ids.json b/rust/sealevel/environments/mainnet3/warp-routes/TONY-base-solanamainnet/program-ids.json index a3862b5b63..5b00cbcd18 100644 --- a/rust/sealevel/environments/mainnet3/warp-routes/TONY-base-solanamainnet/program-ids.json +++ b/rust/sealevel/environments/mainnet3/warp-routes/TONY-base-solanamainnet/program-ids.json @@ -1,10 +1,10 @@ { "base": { - "hex": "0x00000000000000000000000054624ca8abea68645b3b39211f90b804d53db680", - "base58": "1111111111112BBkkpUZNJgdH8DEmyDxFt74d4bH" + "hex": "0x000000000000000000000000cc9ece816641c8350db06af375811107b1aa0b9d", + "base58": "1111111111113rLmoQoXh3C1oA8mu6848RKQbnN8" }, "solanamainnet": { - "hex": "0x2efbd8ff6417a50dbcedc18bab4235d4a2aac61af89214ac59545d30e0f86991", - "base58": "4AQVPTCAeLswnjksQdutxUDuxEJxUBwoWmVimGuPtGSt" + "hex": "0xd87c4b79368eea606390118e5e9bfdb4f720d214f8d2fc45745d00e31562f6ed", + "base58": "Fa4zQJCH7id5KL1eFJt2mHyFpUNfCCSkHgtMrLvrRJBN" } } \ No newline at end of file diff --git a/rust/sealevel/environments/mainnet3/warp-routes/TONY-base-solanamainnet/token-config.json b/rust/sealevel/environments/mainnet3/warp-routes/TONY-base-solanamainnet/token-config.json index 1cc9275f26..97438e336f 100644 --- a/rust/sealevel/environments/mainnet3/warp-routes/TONY-base-solanamainnet/token-config.json +++ b/rust/sealevel/environments/mainnet3/warp-routes/TONY-base-solanamainnet/token-config.json @@ -3,7 +3,7 @@ "type": "collateral", "decimals": 18, "token": "0xb22a793a81ff5b6ad37f40d5fe1e0ac4184d52f3", - "foreignDeployment": "0x54624CA8ABEa68645b3B39211F90B804D53dB680" + "foreignDeployment": "0xcc9EcE816641c8350Db06af375811107B1Aa0b9d" }, "solanamainnet": { "type": "synthetic", diff --git a/solidity/.gitignore b/solidity/.gitignore index 6f01b8e770..d7ca1005d0 100644 --- a/solidity/.gitignore +++ b/solidity/.gitignore @@ -15,3 +15,10 @@ docs flattened/ buildArtifact.json fixtures/ +# ZKSync +artifacts-zk +cache-zk +core-utils/zksync/artifacts/output +.zksolc-libraries-cache/ +typechain-types/ +typechain/ \ No newline at end of file diff --git a/solidity/CHANGELOG.md b/solidity/CHANGELOG.md index 6c62444946..4ae0dc5371 100644 --- a/solidity/CHANGELOG.md +++ b/solidity/CHANGELOG.md @@ -1,5 +1,22 @@ # @hyperlane-xyz/core +## 5.10.0 + +### Minor Changes + +- db8c09011: Add ZKSync support and restructure build artifacts: + + - Add ZKSync compilation support + - Restructure typechain directory location to core-utils/typechain + - Add ZKSync-specific artifact generation and exports + - Update build process to handle both standard and ZKSync artifacts + - Add new exports for ZKSync build artifacts and contract types + +### Patch Changes + +- 11cf66c5e: Export empty zksync buildArtifact to satisfy package.json exports + - @hyperlane-xyz/utils@8.3.0 + ## 5.9.2 ### Patch Changes diff --git a/solidity/contracts/PackageVersioned.sol b/solidity/contracts/PackageVersioned.sol index 9125a85e57..6c5fb707b4 100644 --- a/solidity/contracts/PackageVersioned.sol +++ b/solidity/contracts/PackageVersioned.sol @@ -7,5 +7,5 @@ pragma solidity >=0.6.11; **/ abstract contract PackageVersioned { // GENERATED CODE - DO NOT EDIT - string public constant PACKAGE_VERSION = "5.9.2"; + string public constant PACKAGE_VERSION = "5.10.0"; } diff --git a/solidity/contracts/token/HypERC20.sol b/solidity/contracts/token/HypERC20.sol index 80e7bb6cd9..a22d1687a3 100644 --- a/solidity/contracts/token/HypERC20.sol +++ b/solidity/contracts/token/HypERC20.sol @@ -37,7 +37,7 @@ contract HypERC20 is ERC20Upgradeable, TokenRouter { _MailboxClient_initialize(_hook, _interchainSecurityModule, _owner); } - function decimals() public view override returns (uint8) { + function decimals() public view virtual override returns (uint8) { return _decimals; } diff --git a/solidity/core-utils/index.ts b/solidity/core-utils/index.ts new file mode 100644 index 0000000000..1fbeb99f89 --- /dev/null +++ b/solidity/core-utils/index.ts @@ -0,0 +1,2 @@ +export * from './typechain/index.js'; +export * from './zksync/index.js'; diff --git a/solidity/core-utils/zksync/buildArtifact.ts b/solidity/core-utils/zksync/buildArtifact.ts new file mode 100644 index 0000000000..cc5a00ac95 --- /dev/null +++ b/solidity/core-utils/zksync/buildArtifact.ts @@ -0,0 +1 @@ +export const buildArtifact: any = {}; diff --git a/solidity/core-utils/zksync/index.ts b/solidity/core-utils/zksync/index.ts new file mode 100644 index 0000000000..ece1dd43c5 --- /dev/null +++ b/solidity/core-utils/zksync/index.ts @@ -0,0 +1,103 @@ +import { promises as fsPromises } from 'fs'; +import path, { join } from 'path'; +import { fileURLToPath } from 'url'; + +/** + * @dev Represents a ZkSync artifact. + */ +export type ZKSyncArtifact = { + contractName: string; + sourceName: string; + abi: any; + bytecode: string; + deployedBytecode: string; + factoryDeps?: Record; +}; + +/** + * @dev A mapping of artifact names to their corresponding ZkSync artifacts. + */ +export type ArtifactMap = { + [key: string]: ZKSyncArtifact; +}; + +// Get the resolved path to the current file +const currentFilePath = fileURLToPath(import.meta.url); +const currentDirectory = path.dirname(currentFilePath); + +/** + * @dev Reads artifact files from the specified directory. + * @param directory The directory to read artifact files from. + * @return An array of artifact file names that end with '.json'. + */ +async function getArtifactFiles(directory: string): Promise { + return fsPromises + .readdir(directory) + .then((files) => files.filter((file) => file.endsWith('.json'))); +} + +/** + * @dev Exports the list of artifact names without the .json extension. + * @return An array of artifact names without the .json extension. + */ +export async function getZKSyncArtifactNames(): Promise { + return getArtifactFiles(join(currentDirectory, 'artifacts')).then((files) => + files.map((file) => file.replace('.json', '')), + ); +} + +/** + * @dev Checks if a ZkSync artifact exists by its name. + * @param name The name of the artifact to check. + * @return True if the artifact exists, false otherwise. + */ +export async function artifactExists(name: string): Promise { + const artifactNames = await getZKSyncArtifactNames(); + return artifactNames.includes(name); +} + +/** + * @dev Loads a ZkSync artifact by its name. + * @param name The name of the artifact to load. + * @return The loaded ZKSyncArtifact or undefined if it cannot be loaded. + */ +export async function loadZKSyncArtifact( + name: string, +): Promise { + try { + const artifactPath = join(currentDirectory, 'artifacts', `${name}.json`); + const artifactContent = await fsPromises.readFile(artifactPath, 'utf-8'); + return JSON.parse(artifactContent); + } catch (error) { + console.error(`Error loading artifact: ${name}`, error); + return undefined; + } +} + +/** + * @dev Loads all ZkSync artifacts into a map. + * @return A map of artifact names to their corresponding ZkSync artifacts. + */ +export async function loadAllZKSyncArtifacts(): Promise { + const zkSyncArtifactMap: ArtifactMap = {}; + const zksyncArtifactNames = await getZKSyncArtifactNames(); + for (const artifactName of zksyncArtifactNames) { + const artifact = await loadZKSyncArtifact(artifactName); + if (artifact) { + zkSyncArtifactMap[artifactName] = artifact; + } + } + + return zkSyncArtifactMap; +} + +/** + * @dev Retrieves a specific ZkSync artifact by its file name. + * @param name The name of the artifact to retrieve. + * @return The loaded ZkSyncArtifact or undefined if it cannot be loaded. + */ +export async function getZKSyncArtifactByName( + name: string, +): Promise { + return loadZKSyncArtifact(name); +} diff --git a/solidity/exportBuildArtifact.sh b/solidity/exportBuildArtifact.sh index e6d5d503b8..c951281911 100755 --- a/solidity/exportBuildArtifact.sh +++ b/solidity/exportBuildArtifact.sh @@ -37,3 +37,42 @@ else echo 'Failed to process build artifact with jq' exit 1 fi + +# ZKSYNC + +if [ "$ZKSYNC" = "true" ]; then + # Define the artifacts directory + artifactsDir="./artifacts-zk/build-info" + # Define the output file + outputFileJson="./dist/zksync/buildArtifact.json" + outputFileJs="./dist/zksync/buildArtifact.js" + outputFileTsd="./dist/zksync/buildArtifact.d.ts" + + # log that we're in the script + echo 'Finding and processing ZKSync hardhat build artifact...' + + # Find most recently modified JSON build artifact + if [ "$(uname)" = "Darwin" ]; then + # for local flow + jsonFiles=$(find "$artifactsDir" -type f -name "*.json" -exec stat -f "%m %N" {} \; | sort -rn | head -n 1 | cut -d' ' -f2-) + else + # for CI flow + jsonFiles=$(find "$artifactsDir" -type f -name "*.json" -exec stat -c "%Y %n" {} \; | sort -rn | head -n 1 | cut -d' ' -f2-) + fi + + if [ ! -f "$jsonFiles" ]; then + echo 'Failed to find ZKSync build artifact' + exit 1 + fi + + # Extract required keys and write to outputFile + if jq -c '{input, solcLongVersion, zk_version: .output.zk_version}' "$jsonFiles" >"$outputFileJson"; then + echo "export const buildArtifact = " >"$outputFileJs" + cat "$outputFileJson" >>"$outputFileJs" + echo "export const buildArtifact: any" >"$outputFileTsd" + echo 'Finished processing ZKSync build artifact.' + else + echo 'Failed to process ZKSync build artifact with jq' + exit 1 + fi +fi \ No newline at end of file diff --git a/solidity/generate-artifact-exports.mjs b/solidity/generate-artifact-exports.mjs new file mode 100755 index 0000000000..97ce7beaa0 --- /dev/null +++ b/solidity/generate-artifact-exports.mjs @@ -0,0 +1,60 @@ +import { promises as fsPromises } from 'fs'; +import { basename, dirname, join } from 'path'; +import { glob } from 'typechain'; +import { fileURLToPath } from 'url'; + +const cwd = process.cwd(); + +/** + * @dev Only includes primary JSON artifacts & excludes debug files and build-info directory + */ +const zksyncArtifacts = glob(cwd, [ + `!./artifacts-zk/!(build-info)/**/*.dbg.json`, + `./artifacts-zk/!(build-info)/**/+([a-zA-Z0-9_]).json`, +]); + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const srcOutputDir = join(__dirname, 'core-utils/zksync/artifacts'); + +// Ensure output directory exists +await fsPromises.mkdir(srcOutputDir, { recursive: true }); + +/** + * @dev Processes a single artifact file + */ +async function processArtifactFile(file) { + const fileName = `${basename(file, '.json')}`; + const outputFile = join(srcOutputDir, `${fileName}.json`); + + // Check if file already exists + const fileExists = await fsPromises + .access(outputFile) + .then(() => true) + .catch(() => false); + + if (fileExists) { + // File already exists, skipping... + // NOTE: Hardhat compiler produces duplicate artifacts when + // shared interfaces/libraries are used across different contracts + // This is expected behavior and we only need one copy of each artifact + return; + } + + const fileContent = await fsPromises.readFile(file, { encoding: 'utf-8' }); + await fsPromises.writeFile(outputFile, fileContent); +} + +/** + * @dev Reads each artifact file and writes it to srcOutputDir concurrently + */ +await Promise.all( + zksyncArtifacts.map(async (file) => { + try { + await processArtifactFile(file); + } catch (error) { + console.error(`Error processing file ${file}:`, error); + } + }), +); diff --git a/solidity/hardhat.config.cts b/solidity/hardhat.config.cts index 6d64d971e6..6fd31c3dc4 100644 --- a/solidity/hardhat.config.cts +++ b/solidity/hardhat.config.cts @@ -22,7 +22,7 @@ module.exports = { currency: 'USD', }, typechain: { - outDir: './types', + outDir: './core-utils/typechain', target: 'ethers-v5', alwaysGenerateOverloads: true, node16Modules: true, diff --git a/solidity/package.json b/solidity/package.json index 50986a878a..d40dfbe23d 100644 --- a/solidity/package.json +++ b/solidity/package.json @@ -1,11 +1,11 @@ { "name": "@hyperlane-xyz/core", "description": "Core solidity contracts for Hyperlane", - "version": "5.9.2", + "version": "5.10.0", "dependencies": { "@arbitrum/nitro-contracts": "^1.2.1", "@eth-optimism/contracts": "^0.6.0", - "@hyperlane-xyz/utils": "8.2.0", + "@hyperlane-xyz/utils": "8.3.0", "@layerzerolabs/lz-evm-oapp-v2": "2.0.2", "@openzeppelin/contracts": "^4.9.3", "@openzeppelin/contracts-upgradeable": "^4.9.3", @@ -13,6 +13,7 @@ }, "devDependencies": { "@layerzerolabs/solidity-examples": "^1.1.0", + "@matterlabs/hardhat-zksync-solc": "^1.2.4", "@nomiclabs/hardhat-ethers": "^2.2.3", "@nomiclabs/hardhat-waffle": "^2.0.6", "@typechain/ethers-v5": "^11.1.2", @@ -35,7 +36,8 @@ "ts-node": "^10.8.0", "tsx": "^4.19.1", "typechain": "patch:typechain@npm%3A8.3.2#~/.yarn/patches/typechain-npm-8.3.2-b02e27439e.patch", - "typescript": "5.3.3" + "typescript": "5.3.3", + "zksync-ethers": "^5.10.0" }, "directories": { "test": "test" @@ -43,9 +45,10 @@ "type": "module", "exports": { ".": "./dist/index.js", - "./mailbox": "./dist/contracts/Mailbox.js", + "./mailbox": "./dist/typechain/contracts/Mailbox.js", "./buildArtifact.js": "./dist/buildArtifact.js", "./buildArtifact.json": "./dist/buildArtifact.json", + "./buildArtifact-zksync.js": "./dist/zksync/buildArtifact.js", "./contracts": "./contracts" }, "types": "./dist/index.d.ts", @@ -65,12 +68,15 @@ "license": "Apache-2.0", "scripts": { "build": "yarn version:update && yarn hardhat-esm compile && tsc && ./exportBuildArtifact.sh", + "build:zk": "yarn hardhat-zk compile && ts-node generate-artifact-exports.mjs && ZKSYNC=true ./exportBuildArtifact.sh && yarn copy-artifacts", + "prepublishOnly": "yarn build && yarn build:zk", "lint": "solhint contracts/**/*.sol", - "clean": "yarn hardhat-esm clean && rm -rf ./dist ./cache ./types ./coverage ./out ./forge-cache ./fixtures", + "clean": "yarn hardhat-esm clean && yarn hardhat-zk clean && rm -rf ./dist ./cache ./cache-zk ./types ./coverage ./out ./forge-cache ./fixtures", "coverage": "yarn fixtures && ./coverage.sh", "docs": "forge doc", "fixtures": "mkdir -p ./fixtures/aggregation ./fixtures/multisig", "hardhat-esm": "NODE_OPTIONS='--experimental-loader ts-node/esm/transpile-only --no-warnings=ExperimentalWarning' hardhat --config hardhat.config.cts", + "hardhat-zk": "NODE_OPTIONS='--experimental-loader ts-node/esm/transpile-only --no-warnings=ExperimentalWarning' hardhat --config zk-hardhat.config.cts", "prettier": "prettier --write ./contracts ./test", "test": "yarn version:exhaustive && yarn hardhat-esm test && yarn test:forge", "test:hardhat": "yarn hardhat-esm test", @@ -82,7 +88,8 @@ "storage": "./storage.sh", "version:update": "sh ./bytecodeversion.sh", "version:changed": "yarn version:update && git diff --exit-code", - "version:exhaustive": "yarn tsx ./test/exhaustiveversion.test.ts" + "version:exhaustive": "yarn tsx ./test/exhaustiveversion.test.ts", + "copy-artifacts": "mkdir -p dist/zksync/artifacts && cp core-utils/zksync/artifacts/*.json dist/zksync/artifacts/" }, "peerDependencies": { "@ethersproject/abi": "*", diff --git a/solidity/test/lib/mailboxes.ts b/solidity/test/lib/mailboxes.ts index 06ae211cf5..bd1d728e36 100644 --- a/solidity/test/lib/mailboxes.ts +++ b/solidity/test/lib/mailboxes.ts @@ -18,8 +18,8 @@ import { LegacyMultisigIsm, TestMailbox, TestMerkleTreeHook, -} from '../../types'; -import { DispatchEvent } from '../../types/contracts/Mailbox'; +} from '../../core-utils/typechain'; +import { DispatchEvent } from '../../core-utils/typechain/contracts/Mailbox'; export type MessageAndProof = { proof: MerkleProof; diff --git a/solidity/test/merkle.test.ts b/solidity/test/merkle.test.ts index f2a3d5d399..5546ac86bf 100644 --- a/solidity/test/merkle.test.ts +++ b/solidity/test/merkle.test.ts @@ -2,7 +2,7 @@ import { expect } from 'chai'; import { utils } from 'ethers'; import merkleTestCases from '../../vectors/merkle.json' assert { type: 'json' }; -import { TestMerkle, TestMerkle__factory } from '../types'; +import { TestMerkle, TestMerkle__factory } from '../core-utils/typechain'; import { getSigner } from './signer'; diff --git a/solidity/test/message.test.ts b/solidity/test/message.test.ts index 83bde28b6f..996bb31e7b 100644 --- a/solidity/test/message.test.ts +++ b/solidity/test/message.test.ts @@ -8,7 +8,11 @@ import { } from '@hyperlane-xyz/utils'; import testCases from '../../vectors/message.json' assert { type: 'json' }; -import { Mailbox__factory, TestMessage, TestMessage__factory } from '../types'; +import { + Mailbox__factory, + TestMessage, + TestMessage__factory, +} from '../core-utils/typechain'; import { getSigner, getSigners } from './signer'; diff --git a/solidity/test/mockMailbox.test.ts b/solidity/test/mockMailbox.test.ts index db49a7585e..6df9ec41db 100644 --- a/solidity/test/mockMailbox.test.ts +++ b/solidity/test/mockMailbox.test.ts @@ -3,7 +3,10 @@ import { utils } from 'ethers'; import { addressToBytes32 } from '@hyperlane-xyz/utils'; -import { MockMailbox__factory, TestRecipient__factory } from '../types'; +import { + MockMailbox__factory, + TestRecipient__factory, +} from '../core-utils/typechain'; import { getSigner } from './signer'; diff --git a/solidity/test/testrecipient.test.ts b/solidity/test/testrecipient.test.ts index acdbf573eb..dd1d9a44e1 100644 --- a/solidity/test/testrecipient.test.ts +++ b/solidity/test/testrecipient.test.ts @@ -3,7 +3,7 @@ import { utils } from 'ethers'; import { addressToBytes32 } from '@hyperlane-xyz/utils'; -import { TestRecipient, TestRecipient__factory } from '../types'; +import { TestRecipient, TestRecipient__factory } from '../core-utils/typechain'; import { getSigner } from './signer'; diff --git a/solidity/tsconfig.json b/solidity/tsconfig.json index cece6d7fc6..99ae657d2b 100644 --- a/solidity/tsconfig.json +++ b/solidity/tsconfig.json @@ -2,7 +2,7 @@ "extends": "../tsconfig.json", "compilerOptions": { "outDir": "./dist", - "rootDir": "./types" + "rootDir": "./core-utils" }, - "exclude": ["./test", "hardhat.config.cts", "./dist"] + "exclude": ["./test", "hardhat.config.cts", "./dist", "zk-hardhat.config.cts"] } diff --git a/solidity/zk-hardhat.config.cts b/solidity/zk-hardhat.config.cts new file mode 100644 index 0000000000..618fe65a5b --- /dev/null +++ b/solidity/zk-hardhat.config.cts @@ -0,0 +1,46 @@ +import '@matterlabs/hardhat-zksync-solc'; +import '@nomiclabs/hardhat-ethers'; +import 'hardhat-ignore-warnings'; + +/** + * @type import('hardhat/config').HardhatUserConfig + */ +module.exports = { + zksolc: { + version: '1.5.3', + compilerSource: 'binary', + enableEraVMExtensions: true, + }, + defaultNetwork: 'zkSyncNetwork', + networks: { + zkSyncNetwork: { + url: 'http://127.0.0.1:8011', + ethNetwork: '', + zksync: true, + }, + }, + solidity: { + version: '0.8.19', + settings: { + optimizer: { + enabled: true, + runs: 999_999, + }, + }, + }, + mocha: { + bail: true, + import: 'tsx', + }, + warnings: { + // turn off all warnings for libs: + 'fx-portal/**/*': { + default: 'off', + }, + }, + paths: { + sources: './contracts', + cache: './cache-zk', + artifacts: './artifacts-zk', + }, +}; diff --git a/typescript/ccip-server/CHANGELOG.md b/typescript/ccip-server/CHANGELOG.md index cc7e66f9fc..0ebc68820a 100644 --- a/typescript/ccip-server/CHANGELOG.md +++ b/typescript/ccip-server/CHANGELOG.md @@ -1,5 +1,7 @@ # @hyperlane-xyz/ccip-server +## 8.3.0 + ## 8.2.0 ## 8.1.0 diff --git a/typescript/ccip-server/package.json b/typescript/ccip-server/package.json index 696d522892..eb365fe11f 100644 --- a/typescript/ccip-server/package.json +++ b/typescript/ccip-server/package.json @@ -1,6 +1,6 @@ { "name": "@hyperlane-xyz/ccip-server", - "version": "8.2.0", + "version": "8.3.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 d2ca5e2242..770b48ee69 100644 --- a/typescript/cli/CHANGELOG.md +++ b/typescript/cli/CHANGELOG.md @@ -1,5 +1,18 @@ # @hyperlane-xyz/cli +## 8.3.0 + +### Minor Changes + +- 228f7c3d1: Fix issue where warp deploy artifacts did not include correct symbols. + +### Patch Changes + +- Updated dependencies [7546c0181] +- Updated dependencies [49856fbb9] + - @hyperlane-xyz/sdk@8.3.0 + - @hyperlane-xyz/utils@8.3.0 + ## 8.2.0 ### Minor Changes diff --git a/typescript/cli/package.json b/typescript/cli/package.json index cea798237b..a567392e42 100644 --- a/typescript/cli/package.json +++ b/typescript/cli/package.json @@ -1,13 +1,13 @@ { "name": "@hyperlane-xyz/cli", - "version": "8.2.0", + "version": "8.3.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": "7.1.0", - "@hyperlane-xyz/sdk": "8.2.0", - "@hyperlane-xyz/utils": "8.2.0", + "@hyperlane-xyz/sdk": "8.3.0", + "@hyperlane-xyz/utils": "8.3.0", "@inquirer/core": "9.0.10", "@inquirer/figures": "1.0.5", "@inquirer/prompts": "3.3.2", diff --git a/typescript/cli/src/context/strategies/chain/MultiChainResolver.ts b/typescript/cli/src/context/strategies/chain/MultiChainResolver.ts index 2bdd6f3d1a..b70d787891 100644 --- a/typescript/cli/src/context/strategies/chain/MultiChainResolver.ts +++ b/typescript/cli/src/context/strategies/chain/MultiChainResolver.ts @@ -68,7 +68,7 @@ export class MultiChainResolver implements ChainResolver { argv.config ||= DEFAULT_WARP_ROUTE_DEPLOYMENT_CONFIG_PATH; argv.context.chains = await this.getWarpRouteConfigChains( argv.config.trim(), - argv.skipConfirmation, + argv.context.skipConfirmation, ); return argv.context.chains; } diff --git a/typescript/cli/src/tests/commands/helpers.ts b/typescript/cli/src/tests/commands/helpers.ts index 7fdb407324..977cc89393 100644 --- a/typescript/cli/src/tests/commands/helpers.ts +++ b/typescript/cli/src/tests/commands/helpers.ts @@ -1,8 +1,15 @@ import { ethers } from 'ethers'; import { $, ProcessOutput, ProcessPromise } from 'zx'; -import { ERC20Test__factory, ERC4626Test__factory } from '@hyperlane-xyz/core'; -import { ChainAddresses } from '@hyperlane-xyz/registry'; +import { + ERC20Test, + ERC20Test__factory, + ERC4626Test__factory, +} from '@hyperlane-xyz/core'; +import { + ChainAddresses, + createWarpRouteConfigId, +} from '@hyperlane-xyz/registry'; import { HypTokenRouterConfig, WarpCoreConfig, @@ -41,8 +48,19 @@ export const CHAIN_3_METADATA_PATH = `${REGISTRY_PATH}/chains/${CHAIN_NAME_3}/me export const WARP_CONFIG_PATH_EXAMPLE = `${EXAMPLES_PATH}/warp-route-deployment.yaml`; export const WARP_CONFIG_PATH_2 = `${TEMP_PATH}/${CHAIN_NAME_2}/warp-route-deployment-anvil2.yaml`; +export const WARP_DEPLOY_OUTPUT_PATH = `${TEMP_PATH}/warp-route-deployment.yaml`; export const WARP_CORE_CONFIG_PATH_2 = `${REGISTRY_PATH}/deployments/warp_routes/ETH/anvil2-config.yaml`; +export function getCombinedWarpRoutePath( + tokenSymbol: string, + chains: string[], +): string { + return `${REGISTRY_PATH}/deployments/warp_routes/${createWarpRouteConfigId( + tokenSymbol.toUpperCase(), + chains, + )}-config.yaml`; +} + export const DEFAULT_E2E_TEST_TIMEOUT = 100_000; // Long timeout since these tests can take a while export enum KeyBoardKeys { @@ -258,7 +276,8 @@ export async function deployToken( privateKey: string, chain: string, decimals = 18, -) { + symbol = 'TOKEN', +): Promise { const { multiProvider } = await getContext({ registryUri: REGISTRY_PATH, registryOverrideUri: '', @@ -270,7 +289,12 @@ export async function deployToken( const token = await new ERC20Test__factory( multiProvider.getSigner(chain), - ).deploy('token', 'token', '100000000000000000000', decimals); + ).deploy( + 'token', + symbol.toLocaleUpperCase(), + '100000000000000000000', + decimals, + ); await token.deployed(); return token; diff --git a/typescript/cli/src/tests/commands/warp.ts b/typescript/cli/src/tests/commands/warp.ts index 467446e045..6f3a5d56f3 100644 --- a/typescript/cli/src/tests/commands/warp.ts +++ b/typescript/cli/src/tests/commands/warp.ts @@ -1,6 +1,13 @@ import { $, ProcessPromise } from 'zx'; -import { WarpRouteDeployConfig } from '@hyperlane-xyz/sdk'; +import { + ChainName, + HypTokenRouterConfig, + TokenType, + WarpRouteDeployConfig, + WarpRouteDeployConfigSchema, +} from '@hyperlane-xyz/sdk'; +import { Address } from '@hyperlane-xyz/utils'; import { readYamlOrJson } from '../../utils/files.js'; @@ -25,15 +32,37 @@ export function hyperlaneWarpInit(warpCorePath: string): ProcessPromise { /** * Deploys the Warp route to the specified chain using the provided config. */ -export async function hyperlaneWarpDeploy(warpCorePath: string) { - // --overrides is " " to allow local testing to work - return $`yarn workspace @hyperlane-xyz/cli run hyperlane warp deploy \ +export function hyperlaneWarpDeployRaw({ + warpCorePath, + hypKey, + skipConfirmationPrompts, + privateKey, +}: { + warpCorePath?: string; + hypKey?: string; + skipConfirmationPrompts?: boolean; + privateKey?: string; +}): ProcessPromise { + return $`${ + hypKey ? ['HYP_KEY=' + hypKey] : '' + } yarn workspace @hyperlane-xyz/cli run hyperlane warp deploy \ --registry ${REGISTRY_PATH} \ --overrides " " \ - --config ${warpCorePath} \ - --key ${ANVIL_KEY} \ + ${warpCorePath ? ['--config', warpCorePath] : ''} \ + ${privateKey ? ['--key', privateKey] : ''} \ --verbosity debug \ - --yes`; + ${skipConfirmationPrompts ? ['--yes'] : ''}`; +} + +/** + * Deploys the Warp route to the specified chain using the provided config. + */ +export function hyperlaneWarpDeploy(warpCorePath: string): ProcessPromise { + return hyperlaneWarpDeployRaw({ + privateKey: ANVIL_KEY, + warpCorePath: warpCorePath, + skipConfirmationPrompts: true, + }); } /** @@ -55,27 +84,83 @@ export async function hyperlaneWarpApply( --yes`; } -export async function hyperlaneWarpRead( +export function hyperlaneWarpReadRaw({ + chain, + warpAddress, + outputPath, + privateKey, + symbol, +}: { + chain?: string; + symbol?: string; + privateKey?: string; + warpAddress?: string; + outputPath?: string; +}): ProcessPromise { + return $`yarn workspace @hyperlane-xyz/cli run hyperlane warp read \ + --registry ${REGISTRY_PATH} \ + --overrides " " \ + ${warpAddress ? ['--address', warpAddress] : ''} \ + ${chain ? ['--chain', chain] : ''} \ + ${symbol ? ['--symbol', symbol] : ''} \ + ${privateKey ? ['--key', privateKey] : ''} \ + --verbosity debug \ + ${outputPath ? ['--config', outputPath] : ''}`; +} + +export function hyperlaneWarpRead( chain: string, warpAddress: string, warpDeployPath: string, -) { - return $`yarn workspace @hyperlane-xyz/cli run hyperlane warp read \ +): ProcessPromise { + return hyperlaneWarpReadRaw({ + chain, + warpAddress, + outputPath: warpDeployPath, + privateKey: ANVIL_KEY, + }); +} + +export function hyperlaneWarpCheckRaw({ + warpDeployPath, + symbol, + privateKey, + hypKey, +}: { + symbol?: string; + privateKey?: string; + warpDeployPath?: string; + hypKey?: string; +}): ProcessPromise { + return $`${ + hypKey && !privateKey ? ['HYP_KEY=' + hypKey] : '' + } yarn workspace @hyperlane-xyz/cli run hyperlane warp check \ --registry ${REGISTRY_PATH} \ --overrides " " \ - --address ${warpAddress} \ - --chain ${chain} \ - --key ${ANVIL_KEY} \ + ${symbol ? ['--symbol', symbol] : ''} \ + ${privateKey && !hypKey ? ['--key', privateKey] : ''} \ --verbosity debug \ - --config ${warpDeployPath}`; + ${warpDeployPath ? ['--config', warpDeployPath] : ''}`; } -export async function hyperlaneWarpSendRelay( +export function hyperlaneWarpCheck( + warpDeployPath: string, + symbol: string, +): ProcessPromise { + return hyperlaneWarpCheckRaw({ + warpDeployPath, + privateKey: ANVIL_KEY, + symbol, + }); +} + +export function hyperlaneWarpSendRelay( origin: string, destination: string, warpCorePath: string, relay = true, -) { + value = 1, +): ProcessPromise { return $`yarn workspace @hyperlane-xyz/cli run hyperlane warp send \ ${relay ? '--relay' : ''} \ --registry ${REGISTRY_PATH} \ @@ -85,7 +170,8 @@ export async function hyperlaneWarpSendRelay( --warp ${warpCorePath} \ --key ${ANVIL_KEY} \ --verbosity debug \ - --yes`; + --yes \ + --amount ${value}`; } /** @@ -103,3 +189,169 @@ export async function readWarpConfig( await hyperlaneWarpRead(chain, warpAddress!, warpDeployPath); return readYamlOrJson(warpDeployPath); } + +type GetWarpTokenConfigByTokenTypeOptions = { + tokenType: TokenType; + mailbox: Address; + owner: Address; + token: Address; + vault: Address; + otherChain: ChainName; +}; + +function getWarpTokenConfigForType({ + mailbox, + otherChain, + owner, + token, + tokenType, + vault, +}: GetWarpTokenConfigByTokenTypeOptions): HypTokenRouterConfig { + let tokenConfig: HypTokenRouterConfig; + switch (tokenType) { + case TokenType.collateral: + tokenConfig = { + type: TokenType.collateral, + mailbox, + owner, + token, + }; + break; + case TokenType.collateralVault: + tokenConfig = { + type: TokenType.collateralVault, + mailbox, + owner, + token: vault, + }; + break; + case TokenType.collateralVaultRebase: + tokenConfig = { + type: TokenType.collateralVaultRebase, + mailbox, + owner, + token: vault, + }; + break; + case TokenType.fastCollateral: + tokenConfig = { + type: TokenType.fastCollateral, + mailbox, + owner, + token, + }; + break; + case TokenType.fastSynthetic: + tokenConfig = { + type: TokenType.fastSynthetic, + mailbox, + owner, + }; + break; + case TokenType.native: + tokenConfig = { + type: TokenType.native, + mailbox, + owner, + }; + break; + case TokenType.nativeScaled: + tokenConfig = { + type: TokenType.nativeScaled, + mailbox, + owner, + scale: 1, + }; + break; + case TokenType.synthetic: + tokenConfig = { + type: TokenType.synthetic, + mailbox, + owner, + }; + break; + case TokenType.syntheticRebase: + tokenConfig = { + type: TokenType.syntheticRebase, + mailbox, + owner, + collateralChainName: otherChain, + }; + break; + default: + throw new Error( + `Unsupported token type "${tokenType}" for random config generation`, + ); + } + + return tokenConfig; +} + +type GetWarpTokenConfigOptions = { + mailbox: Address; + owner: Address; + token: Address; + vault: Address; + chainName: ChainName; +}; + +export function generateWarpConfigs( + chain1Config: GetWarpTokenConfigOptions, + chain2Config: GetWarpTokenConfigOptions, +): ReadonlyArray { + const ignoreTokenTypes = new Set([ + TokenType.XERC20, + TokenType.XERC20Lockbox, + TokenType.collateralFiat, + TokenType.collateralUri, + TokenType.syntheticUri, + // TODO Fix: sender not mailbox or relaying simply fails + TokenType.collateralVault, + ]); + + const allowedWarpTokenTypes = Object.values(TokenType).filter( + (tokenType) => + !ignoreTokenTypes.has(tokenType) && typeof tokenType === 'string', + ); + + const exists = new Set([]); + const configs: WarpRouteDeployConfig[] = allowedWarpTokenTypes + .flatMap((tokenType) => + allowedWarpTokenTypes.map((otherTokenType) => { + return { + [chain1Config.chainName]: getWarpTokenConfigForType({ + ...chain1Config, + tokenType: tokenType, + otherChain: chain2Config.chainName, + }), + [chain2Config.chainName]: getWarpTokenConfigForType({ + ...chain2Config, + tokenType: otherTokenType, + otherChain: chain1Config.chainName, + }), + }; + }), + ) + // Remove already existing config pairs + .filter((config) => { + const combinationId: string = [ + config[chain1Config.chainName].type, + config[chain2Config.chainName].type, + ] + .sort() + .join(''); + + if (exists.has(combinationId)) { + return false; + } + + exists.add(combinationId); + return true; + }) + // Remove invalid configs + .filter( + (warpConfig) => WarpRouteDeployConfigSchema.safeParse(warpConfig).success, + ); + + return configs; +} diff --git a/typescript/cli/src/tests/core/core-check.e2e-test.ts b/typescript/cli/src/tests/core/core-check.e2e-test.ts index f1dd609914..30e4490af9 100644 --- a/typescript/cli/src/tests/core/core-check.e2e-test.ts +++ b/typescript/cli/src/tests/core/core-check.e2e-test.ts @@ -50,7 +50,7 @@ describe('hyperlane core check e2e tests', async function () { ); expect(output.exitCode).to.equal(0); - expect(output.text().includes('No violations found')).to.be.true; + expect(output.text()).to.includes('No violations found'); }); it('should find differences between the local and onchain config', async () => { @@ -69,8 +69,8 @@ describe('hyperlane core check e2e tests', async function () { ).nothrow(); expect(output.exitCode).to.equal(1); - expect(output.text().includes(expectedDiffText)).to.be.true; - expect(output.text().includes(expectedActualText)).to.be.true; + expect(output.text()).to.include(expectedDiffText); + expect(output.text()).to.include(expectedActualText); }); it('should successfully check the config when provided with a custom mailbox', async () => { @@ -88,6 +88,6 @@ describe('hyperlane core check e2e tests', async function () { ); expect(output.exitCode).to.equal(0); - expect(output.text().includes('No violations found')).to.be.true; + expect(output.text()).to.includes('No violations found'); }); }); diff --git a/typescript/cli/src/tests/warp-deploy.e2e-test.ts b/typescript/cli/src/tests/warp-deploy.e2e-test.ts deleted file mode 100644 index 71dc2e9183..0000000000 --- a/typescript/cli/src/tests/warp-deploy.e2e-test.ts +++ /dev/null @@ -1,250 +0,0 @@ -import * as chai from 'chai'; -import chaiAsPromised from 'chai-as-promised'; - -import { ChainAddresses } from '@hyperlane-xyz/registry'; -import { - HookConfig, - HookType, - IsmConfig, - IsmType, - TokenType, - WarpRouteDeployConfig, - normalizeConfig, -} from '@hyperlane-xyz/sdk'; - -import { WarpSendLogs } from '../send/transfer.js'; -import { writeYamlOrJson } from '../utils/files.js'; - -import { - ANVIL_KEY, - CHAIN_NAME_2, - CHAIN_NAME_3, - CORE_CONFIG_PATH, - DEFAULT_E2E_TEST_TIMEOUT, - REGISTRY_PATH, - TEMP_PATH, - deploy4626Vault, - deployOrUseExistingCore, - deployToken, - sendWarpRouteMessageRoundTrip, -} from './commands/helpers.js'; -import { - hyperlaneWarpDeploy, - hyperlaneWarpSendRelay, - readWarpConfig, -} from './commands/warp.js'; - -chai.use(chaiAsPromised); -const expect = chai.expect; -chai.should(); - -const WARP_CONFIG_PATH = `${TEMP_PATH}/warp-route-deployment-deploy.yaml`; -const WARP_CORE_CONFIG_PATH_2_3 = `${REGISTRY_PATH}/deployments/warp_routes/VAULT/anvil2-anvil3-config.yaml`; - -describe('hyperlane warp deploy e2e tests', async function () { - this.timeout(DEFAULT_E2E_TEST_TIMEOUT); - - let chain2Addresses: ChainAddresses = {}; - let chain3Addresses: ChainAddresses = {}; - let token: any; - let vault: any; - - before(async function () { - [chain2Addresses, chain3Addresses] = await Promise.all([ - deployOrUseExistingCore(CHAIN_NAME_2, CORE_CONFIG_PATH, ANVIL_KEY), - deployOrUseExistingCore(CHAIN_NAME_3, CORE_CONFIG_PATH, ANVIL_KEY), - ]); - - token = await deployToken(ANVIL_KEY, CHAIN_NAME_2); - vault = await deploy4626Vault(ANVIL_KEY, CHAIN_NAME_2, token.address); - }); - - it('should only allow rebasing yield route to be deployed with rebasing synthetic', async function () { - const warpConfig: WarpRouteDeployConfig = { - [CHAIN_NAME_2]: { - type: TokenType.collateralVaultRebase, - token: vault.address, - mailbox: chain2Addresses.mailbox, - owner: chain2Addresses.mailbox, - }, - [CHAIN_NAME_3]: { - type: TokenType.synthetic, - mailbox: chain2Addresses.mailbox, - owner: chain2Addresses.mailbox, - }, - }; - - writeYamlOrJson(WARP_CONFIG_PATH, warpConfig); - await hyperlaneWarpDeploy(WARP_CONFIG_PATH).should.be.rejected; // TODO: revisit this to figure out how to parse the error. - }); - - it(`should be able to bridge between ${TokenType.collateralVaultRebase} and ${TokenType.syntheticRebase}`, async function () { - const warpConfig: WarpRouteDeployConfig = { - [CHAIN_NAME_2]: { - type: TokenType.collateralVaultRebase, - token: vault.address, - mailbox: chain2Addresses.mailbox, - owner: chain2Addresses.mailbox, - }, - [CHAIN_NAME_3]: { - type: TokenType.syntheticRebase, - mailbox: chain3Addresses.mailbox, - owner: chain3Addresses.mailbox, - collateralChainName: CHAIN_NAME_2, - }, - }; - - writeYamlOrJson(WARP_CONFIG_PATH, warpConfig); - await hyperlaneWarpDeploy(WARP_CONFIG_PATH); - - // Check collateralRebase - const collateralRebaseConfig = ( - await readWarpConfig( - CHAIN_NAME_2, - WARP_CORE_CONFIG_PATH_2_3, - WARP_CONFIG_PATH, - ) - )[CHAIN_NAME_2]; - - expect(collateralRebaseConfig.type).to.equal( - TokenType.collateralVaultRebase, - ); - - // Try to send a transaction - const { stdout } = await sendWarpRouteMessageRoundTrip( - CHAIN_NAME_2, - CHAIN_NAME_3, - WARP_CORE_CONFIG_PATH_2_3, - ); - expect(stdout).to.include(WarpSendLogs.SUCCESS); - }); - - it('should deploy with an ISM config', async () => { - // 1. Define ISM configuration - const ism: IsmConfig = { - type: IsmType.MESSAGE_ID_MULTISIG, - validators: [chain2Addresses.mailbox], // Using mailbox address as example validator - threshold: 1, - }; - - // 2. Create Warp configuration with ISM - const warpConfig: WarpRouteDeployConfig = { - [CHAIN_NAME_2]: { - type: TokenType.collateralVaultRebase, - token: vault.address, - mailbox: chain2Addresses.mailbox, - owner: chain2Addresses.mailbox, - interchainSecurityModule: ism, // Add ISM config here - }, - [CHAIN_NAME_3]: { - type: TokenType.syntheticRebase, - mailbox: chain3Addresses.mailbox, - owner: chain3Addresses.mailbox, - collateralChainName: CHAIN_NAME_2, - }, - }; - - // 3. Write config and deploy - writeYamlOrJson(WARP_CONFIG_PATH, warpConfig); - await hyperlaneWarpDeploy(WARP_CONFIG_PATH); - - // 4. Verify deployed ISM configuration - const collateralRebaseConfig = ( - await readWarpConfig( - CHAIN_NAME_2, - WARP_CORE_CONFIG_PATH_2_3, - WARP_CONFIG_PATH, - ) - )[CHAIN_NAME_2]; - - expect( - normalizeConfig(collateralRebaseConfig.interchainSecurityModule), - ).to.deep.equal(normalizeConfig(ism)); - }); - - it('should deploy with a hook config', async () => { - const hook: HookConfig = { - type: HookType.PROTOCOL_FEE, - beneficiary: chain2Addresses.mailbox, - owner: chain2Addresses.mailbox, - maxProtocolFee: '1337', - protocolFee: '1337', - }; - const warpConfig: WarpRouteDeployConfig = { - [CHAIN_NAME_2]: { - type: TokenType.collateralVaultRebase, - token: vault.address, - mailbox: chain2Addresses.mailbox, - owner: chain2Addresses.mailbox, - hook, - }, - [CHAIN_NAME_3]: { - type: TokenType.syntheticRebase, - mailbox: chain3Addresses.mailbox, - owner: chain3Addresses.mailbox, - collateralChainName: CHAIN_NAME_2, - }, - }; - - writeYamlOrJson(WARP_CONFIG_PATH, warpConfig); - await hyperlaneWarpDeploy(WARP_CONFIG_PATH); - - // Check collateralRebase - const collateralRebaseConfig = ( - await readWarpConfig( - CHAIN_NAME_2, - WARP_CORE_CONFIG_PATH_2_3, - WARP_CONFIG_PATH, - ) - )[CHAIN_NAME_2]; - - expect(normalizeConfig(collateralRebaseConfig.hook)).to.deep.equal( - normalizeConfig(hook), - ); - }); - - it('should send a message from origin to destination in the correct order', async function () { - const warpConfig: WarpRouteDeployConfig = { - [CHAIN_NAME_2]: { - type: TokenType.collateralVaultRebase, - token: vault.address, - mailbox: chain2Addresses.mailbox, - owner: chain2Addresses.mailbox, - }, - [CHAIN_NAME_3]: { - type: TokenType.syntheticRebase, - mailbox: chain3Addresses.mailbox, - owner: chain3Addresses.mailbox, - collateralChainName: CHAIN_NAME_2, - }, - }; - - writeYamlOrJson(WARP_CONFIG_PATH, warpConfig); - await hyperlaneWarpDeploy(WARP_CONFIG_PATH); - - // Try to send a transaction with the origin destination - const { stdout: chain2Tochain3Stdout } = await hyperlaneWarpSendRelay( - CHAIN_NAME_2, - CHAIN_NAME_3, - WARP_CORE_CONFIG_PATH_2_3, - ); - expect(chain2Tochain3Stdout).to.include('anvil2 ➡️ anvil3'); - - // Send another message with swapped origin destination - const { stdout: chain3Tochain2Stdout } = await hyperlaneWarpSendRelay( - CHAIN_NAME_3, - CHAIN_NAME_2, - WARP_CORE_CONFIG_PATH_2_3, - ); - expect(chain3Tochain2Stdout).to.include('anvil3 ➡️ anvil2'); - - // Should throw if invalid origin or destination - await hyperlaneWarpSendRelay( - 'anvil1', - CHAIN_NAME_3, - WARP_CORE_CONFIG_PATH_2_3, - ).should.be.rejectedWith( - 'Error: Origin (anvil1) or destination (anvil3) are not part of the warp route.', - ); - }); -}); diff --git a/typescript/cli/src/tests/warp-read.e2e-test.ts b/typescript/cli/src/tests/warp-read.e2e-test.ts deleted file mode 100644 index 6b7096ad26..0000000000 --- a/typescript/cli/src/tests/warp-read.e2e-test.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { expect } from 'chai'; - -import { WarpRouteDeployConfig } from '@hyperlane-xyz/sdk'; - -import { readYamlOrJson, writeYamlOrJson } from '../utils/files.js'; - -import { - ANVIL_KEY, - CHAIN_NAME_2, - CORE_CONFIG_PATH, - DEFAULT_E2E_TEST_TIMEOUT, - TEMP_PATH, - WARP_CONFIG_PATH_2, - WARP_CONFIG_PATH_EXAMPLE, - WARP_CORE_CONFIG_PATH_2, - deployOrUseExistingCore, -} from './commands/helpers.js'; -import { hyperlaneWarpDeploy, readWarpConfig } from './commands/warp.js'; - -describe('hyperlane warp read e2e tests', async function () { - this.timeout(DEFAULT_E2E_TEST_TIMEOUT); - - let anvil2Config: WarpRouteDeployConfig; - - before(async function () { - await deployOrUseExistingCore(CHAIN_NAME_2, CORE_CONFIG_PATH, ANVIL_KEY); - - // Create a new warp config using the example - const exampleWarpConfig: WarpRouteDeployConfig = readYamlOrJson( - WARP_CONFIG_PATH_EXAMPLE, - ); - anvil2Config = { anvil2: { ...exampleWarpConfig.anvil1 } }; - writeYamlOrJson(WARP_CONFIG_PATH_2, anvil2Config); - }); - - beforeEach(async function () { - await hyperlaneWarpDeploy(WARP_CONFIG_PATH_2); - }); - - it('should be able to read a warp route', async function () { - const warpConfigPath = `${TEMP_PATH}/warp-route-deployment-2.yaml`; - const warpConfig = await readWarpConfig( - CHAIN_NAME_2, - WARP_CORE_CONFIG_PATH_2, - warpConfigPath, - ); - expect(warpConfig[CHAIN_NAME_2].type).to.be.equal( - anvil2Config[CHAIN_NAME_2].type, - ); - }); -}); diff --git a/typescript/cli/src/tests/warp-apply.e2e-test.ts b/typescript/cli/src/tests/warp/warp-apply.e2e-test.ts similarity index 94% rename from typescript/cli/src/tests/warp-apply.e2e-test.ts rename to typescript/cli/src/tests/warp/warp-apply.e2e-test.ts index b5f9bab268..af06f4b660 100644 --- a/typescript/cli/src/tests/warp-apply.e2e-test.ts +++ b/typescript/cli/src/tests/warp/warp-apply.e2e-test.ts @@ -11,8 +11,7 @@ import { randomAddress, } from '@hyperlane-xyz/sdk'; -import { readYamlOrJson, writeYamlOrJson } from '../utils/files.js'; - +import { readYamlOrJson, writeYamlOrJson } from '../../utils/files.js'; import { ANVIL_KEY, CHAIN_NAME_2, @@ -21,21 +20,21 @@ import { DEFAULT_E2E_TEST_TIMEOUT, E2E_TEST_BURN_ADDRESS, EXAMPLES_PATH, - REGISTRY_PATH, TEMP_PATH, WARP_CONFIG_PATH_2, WARP_CONFIG_PATH_EXAMPLE, WARP_CORE_CONFIG_PATH_2, deployOrUseExistingCore, extendWarpConfig, + getCombinedWarpRoutePath, getDomainId, updateOwner, -} from './commands/helpers.js'; +} from '../commands/helpers.js'; import { hyperlaneWarpApply, hyperlaneWarpDeploy, readWarpConfig, -} from './commands/warp.js'; +} from '../commands/warp.js'; describe('hyperlane warp apply e2e tests', async function () { this.timeout(2 * DEFAULT_E2E_TEST_TIMEOUT); @@ -162,7 +161,10 @@ describe('hyperlane warp apply e2e tests', async function () { warpDeployPath: warpConfigPath, }); - const COMBINED_WARP_CORE_CONFIG_PATH = `${REGISTRY_PATH}/deployments/warp_routes/ETH/anvil2-anvil3-config.yaml`; + const COMBINED_WARP_CORE_CONFIG_PATH = getCombinedWarpRoutePath('ETH', [ + CHAIN_NAME_2, + CHAIN_NAME_3, + ]); // Check that chain2 is enrolled in chain1 const updatedWarpDeployConfig1 = await readWarpConfig( @@ -216,7 +218,10 @@ describe('hyperlane warp apply e2e tests', async function () { strategyUrl: `${EXAMPLES_PATH}/submit/strategy/json-rpc-chain-strategy.yaml`, }); - const COMBINED_WARP_CORE_CONFIG_PATH = `${REGISTRY_PATH}/deployments/warp_routes/ETH/anvil2-anvil3-config.yaml`; + const COMBINED_WARP_CORE_CONFIG_PATH = getCombinedWarpRoutePath('ETH', [ + CHAIN_NAME_2, + CHAIN_NAME_3, + ]); // Check that chain2 is enrolled in chain1 const updatedWarpDeployConfig1 = await readWarpConfig( @@ -271,7 +276,10 @@ describe('hyperlane warp apply e2e tests', async function () { writeYamlOrJson(warpDeployPath, warpDeployConfig); await hyperlaneWarpApply(warpDeployPath, WARP_CORE_CONFIG_PATH_2); - const COMBINED_WARP_CORE_CONFIG_PATH = `${REGISTRY_PATH}/deployments/warp_routes/ETH/anvil2-anvil3-config.yaml`; + const COMBINED_WARP_CORE_CONFIG_PATH = getCombinedWarpRoutePath('ETH', [ + CHAIN_NAME_2, + CHAIN_NAME_3, + ]); const updatedWarpDeployConfig_2 = await readWarpConfig( CHAIN_NAME_2, @@ -331,7 +339,10 @@ describe('hyperlane warp apply e2e tests', async function () { writeYamlOrJson(warpConfigPath, warpDeployConfig); await hyperlaneWarpApply(warpConfigPath, WARP_CORE_CONFIG_PATH_2); - const COMBINED_WARP_CORE_CONFIG_PATH = `${REGISTRY_PATH}/deployments/warp_routes/ETH/anvil2-anvil3-config.yaml`; + const COMBINED_WARP_CORE_CONFIG_PATH = getCombinedWarpRoutePath('ETH', [ + CHAIN_NAME_2, + CHAIN_NAME_3, + ]); // Check that chain2 is enrolled in chain1 const updatedWarpDeployConfig_2 = await readWarpConfig( diff --git a/typescript/cli/src/tests/warp/warp-check.e2e-test.ts b/typescript/cli/src/tests/warp/warp-check.e2e-test.ts new file mode 100644 index 0000000000..7d3d8167a7 --- /dev/null +++ b/typescript/cli/src/tests/warp/warp-check.e2e-test.ts @@ -0,0 +1,203 @@ +import { expect } from 'chai'; +import { Wallet } from 'ethers'; + +import { ERC20Test } from '@hyperlane-xyz/core'; +import { ChainAddresses } from '@hyperlane-xyz/registry'; +import { + TokenType, + WarpRouteDeployConfig, + randomAddress, +} from '@hyperlane-xyz/sdk'; +import { Address } from '@hyperlane-xyz/utils'; + +import { writeYamlOrJson } from '../../utils/files.js'; +import { + ANVIL_KEY, + CHAIN_NAME_2, + CHAIN_NAME_3, + CORE_CONFIG_PATH, + DEFAULT_E2E_TEST_TIMEOUT, + KeyBoardKeys, + TestPromptAction, + WARP_DEPLOY_OUTPUT_PATH, + deployOrUseExistingCore, + deployToken, + getCombinedWarpRoutePath, + handlePrompts, +} from '../commands/helpers.js'; +import { + hyperlaneWarpCheck, + hyperlaneWarpCheckRaw, + hyperlaneWarpDeploy, + readWarpConfig, +} from '../commands/warp.js'; + +describe('hyperlane warp check e2e tests', async function () { + this.timeout(2 * DEFAULT_E2E_TEST_TIMEOUT); + + let chain2Addresses: ChainAddresses = {}; + let chain3Addresses: ChainAddresses = {}; + let token: ERC20Test; + let tokenSymbol: string; + let ownerAddress: Address; + + before(async function () { + [chain2Addresses, chain3Addresses] = await Promise.all([ + deployOrUseExistingCore(CHAIN_NAME_2, CORE_CONFIG_PATH, ANVIL_KEY), + deployOrUseExistingCore(CHAIN_NAME_3, CORE_CONFIG_PATH, ANVIL_KEY), + ]); + + token = await deployToken(ANVIL_KEY, CHAIN_NAME_2); + tokenSymbol = await token.symbol(); + ownerAddress = new Wallet(ANVIL_KEY).address; + }); + + async function deployAndExportWarpRoute( + collateralTokenSymbol: string, + collateralTokenAddress: Address, + ): Promise { + const COMBINED_WARP_CORE_CONFIG_PATH = getCombinedWarpRoutePath( + collateralTokenSymbol, + [CHAIN_NAME_2, CHAIN_NAME_3], + ); + const warpConfig: WarpRouteDeployConfig = { + [CHAIN_NAME_2]: { + type: TokenType.collateral, + token: collateralTokenAddress, + mailbox: chain2Addresses.mailbox, + owner: ownerAddress, + }, + [CHAIN_NAME_3]: { + type: TokenType.synthetic, + mailbox: chain3Addresses.mailbox, + owner: ownerAddress, + }, + }; + + writeYamlOrJson(WARP_DEPLOY_OUTPUT_PATH, warpConfig); + await hyperlaneWarpDeploy(WARP_DEPLOY_OUTPUT_PATH); + + const chain2WarpConfig = await readWarpConfig( + CHAIN_NAME_2, + COMBINED_WARP_CORE_CONFIG_PATH, + WARP_DEPLOY_OUTPUT_PATH, + ); + const chain3WarpConfig = await readWarpConfig( + CHAIN_NAME_3, + COMBINED_WARP_CORE_CONFIG_PATH, + WARP_DEPLOY_OUTPUT_PATH, + ); + const warpReadResult = { + [CHAIN_NAME_2]: chain2WarpConfig[CHAIN_NAME_2], + [CHAIN_NAME_3]: chain3WarpConfig[CHAIN_NAME_3], + }; + writeYamlOrJson(WARP_DEPLOY_OUTPUT_PATH, warpReadResult); + + return warpReadResult; + } + + describe('HYP_KEY=... hyperlane warp check --config ...', () => { + it(`should exit early if no symbol, chain or warp file have been provided`, async function () { + await deployAndExportWarpRoute(tokenSymbol, token.address); + + const finalOutput = await hyperlaneWarpCheckRaw({ + hypKey: ANVIL_KEY, + warpDeployPath: WARP_DEPLOY_OUTPUT_PATH, + }) + .stdio('pipe') + .nothrow(); + + expect(finalOutput.exitCode).to.equal(1); + expect(finalOutput.text()).to.include( + 'Please specify either a symbol, chain and address or warp file', + ); + }); + }); + + describe('hyperlane warp check --key ... --config ...', () => { + it(`should exit early if no symbol, chain or warp file have been provided`, async function () { + await deployAndExportWarpRoute(tokenSymbol, token.address); + + const finalOutput = await hyperlaneWarpCheckRaw({ + privateKey: ANVIL_KEY, + warpDeployPath: WARP_DEPLOY_OUTPUT_PATH, + }) + .stdio('pipe') + .nothrow(); + + expect(finalOutput.exitCode).to.equal(1); + expect(finalOutput.text()).to.include( + 'Please specify either a symbol, chain and address or warp file', + ); + }); + }); + + describe('hyperlane warp check --symbol ... --config ...', () => { + it(`should not find any differences between the on chain config and the local one`, async function () { + await deployAndExportWarpRoute(tokenSymbol, token.address); + + const steps: TestPromptAction[] = [ + { + check: (currentOutput) => + currentOutput.includes('Please enter the private key for chain'), + input: `${ANVIL_KEY}${KeyBoardKeys.ENTER}`, + }, + { + check: (currentOutput) => + currentOutput.includes('Please enter the private key for chain'), + input: `${ANVIL_KEY}${KeyBoardKeys.ENTER}`, + }, + ]; + + const output = hyperlaneWarpCheckRaw({ + symbol: tokenSymbol, + warpDeployPath: WARP_DEPLOY_OUTPUT_PATH, + }) + .stdio('pipe') + .nothrow(); + + const finalOutput = await handlePrompts(output, steps); + + expect(finalOutput.exitCode).to.equal(0); + expect(finalOutput.text()).to.include('No violations found'); + }); + }); + + describe('hyperlane warp check --symbol ... --config ... --key ...', () => { + it(`should not find any differences between the on chain config and the local one`, async function () { + await deployAndExportWarpRoute(tokenSymbol, token.address); + + const output = await hyperlaneWarpCheck( + WARP_DEPLOY_OUTPUT_PATH, + tokenSymbol, + ); + + expect(output.exitCode).to.equal(0); + expect(output.text()).to.includes('No violations found'); + }); + + it(`should find differences between the local config and the on chain config`, async function () { + const warpDeployConfig = await deployAndExportWarpRoute( + tokenSymbol, + token.address, + ); + + const wrongOwner = randomAddress(); + warpDeployConfig[CHAIN_NAME_3].owner = wrongOwner; + + const expectedDiffText = `EXPECTED: "${wrongOwner.toLowerCase()}"\n`; + const expectedActualText = `ACTUAL: "${ownerAddress.toLowerCase()}"\n`; + + writeYamlOrJson(WARP_DEPLOY_OUTPUT_PATH, warpDeployConfig); + + const output = await hyperlaneWarpCheck( + WARP_DEPLOY_OUTPUT_PATH, + tokenSymbol, + ).nothrow(); + + expect(output.exitCode).to.equal(1); + expect(output.text().includes(expectedDiffText)).to.be.true; + expect(output.text().includes(expectedActualText)).to.be.true; + }); + }); +}); diff --git a/typescript/cli/src/tests/warp/warp-deploy.e2e-test.ts b/typescript/cli/src/tests/warp/warp-deploy.e2e-test.ts new file mode 100644 index 0000000000..ba79147ca3 --- /dev/null +++ b/typescript/cli/src/tests/warp/warp-deploy.e2e-test.ts @@ -0,0 +1,644 @@ +import { JsonRpcProvider } from '@ethersproject/providers'; +import * as chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import { Wallet } from 'ethers'; +import { parseUnits } from 'ethers/lib/utils.js'; + +import { ERC20Test, ERC4626Test } from '@hyperlane-xyz/core'; +import { ChainAddresses } from '@hyperlane-xyz/registry'; +import { + ChainMap, + ChainMetadata, + ChainName, + HookConfig, + HookType, + IsmConfig, + IsmType, + Token, + TokenType, + WarpCoreConfig, + WarpRouteDeployConfig, + normalizeConfig, +} from '@hyperlane-xyz/sdk'; +import { Address } from '@hyperlane-xyz/utils'; + +import { readYamlOrJson, writeYamlOrJson } from '../../utils/files.js'; +import { + ANVIL_KEY, + CHAIN_2_METADATA_PATH, + CHAIN_3_METADATA_PATH, + CHAIN_NAME_2, + CHAIN_NAME_3, + CORE_CONFIG_PATH, + DEFAULT_E2E_TEST_TIMEOUT, + KeyBoardKeys, + TestPromptAction, + WARP_DEPLOY_OUTPUT_PATH, + deploy4626Vault, + deployOrUseExistingCore, + deployToken, + getCombinedWarpRoutePath, + handlePrompts, + sendWarpRouteMessageRoundTrip, +} from '../commands/helpers.js'; +import { + generateWarpConfigs, + hyperlaneWarpDeploy, + hyperlaneWarpDeployRaw, + hyperlaneWarpSendRelay, + readWarpConfig, +} from '../commands/warp.js'; + +chai.use(chaiAsPromised); +const expect = chai.expect; +chai.should(); + +const WARP_CORE_CONFIG_PATH_2_3 = getCombinedWarpRoutePath('VAULT', [ + CHAIN_NAME_2, + CHAIN_NAME_3, +]); + +describe('hyperlane warp deploy e2e tests', async function () { + this.timeout(DEFAULT_E2E_TEST_TIMEOUT); + + let chain2Addresses: ChainAddresses = {}; + let chain3Addresses: ChainAddresses = {}; + + let ownerAddress: Address; + let walletChain2: Wallet; + let walletChain3: Wallet; + + before(async function () { + const chain2Metadata: ChainMetadata = readYamlOrJson(CHAIN_2_METADATA_PATH); + const chain3Metadata: ChainMetadata = readYamlOrJson(CHAIN_3_METADATA_PATH); + + const providerChain2 = new JsonRpcProvider(chain2Metadata.rpcUrls[0].http); + const providerChain3 = new JsonRpcProvider(chain3Metadata.rpcUrls[0].http); + + walletChain2 = new Wallet(ANVIL_KEY).connect(providerChain2); + walletChain3 = new Wallet(ANVIL_KEY).connect(providerChain3); + + ownerAddress = walletChain2.address; + + [chain2Addresses, chain3Addresses] = await Promise.all([ + deployOrUseExistingCore(CHAIN_NAME_2, CORE_CONFIG_PATH, ANVIL_KEY), + deployOrUseExistingCore(CHAIN_NAME_3, CORE_CONFIG_PATH, ANVIL_KEY), + ]); + }); + + async function assertWarpRouteConfig( + warpDeployConfig: Readonly, + warpCoreConfigPath: string, + chainName: ChainName, + expectedMetadata: { decimals: number; symbol: string }, + ): Promise { + const currentWarpDeployConfig = await readWarpConfig( + chainName, + warpCoreConfigPath, + WARP_DEPLOY_OUTPUT_PATH, + ); + + expect(currentWarpDeployConfig[chainName].type).to.equal( + warpDeployConfig[chainName].type, + ); + expect(currentWarpDeployConfig[chainName].decimals).to.equal( + warpDeployConfig[chainName].decimals ?? expectedMetadata.decimals, + ); + expect(currentWarpDeployConfig[chainName].symbol).to.equal( + warpDeployConfig[chainName].symbol ?? expectedMetadata.symbol, + ); + expect(currentWarpDeployConfig[chainName].mailbox).to.equal( + chain2Addresses.mailbox, + ); + } + + describe('hyperlane warp deploy --config ...', () => { + it(`should exit early when the provided deployment file does not exist`, async function () { + const nonExistingFilePath = 'non-existing-path'; + // Currently if the file provided in the config flag does not exist a prompt will still be shown to the + // user to enter a valid file and then it will finally fail + const steps: TestPromptAction[] = [ + { + check: (currentOutput: string) => + currentOutput.includes('Select Warp route deployment config file'), + input: `${KeyBoardKeys.ARROW_DOWN}${KeyBoardKeys.ENTER}`, + }, + { + check: (currentOutput: string) => + currentOutput.includes( + 'Enter Warp route deployment config filepath', + ), + input: `${nonExistingFilePath}${KeyBoardKeys.ENTER}`, + }, + ]; + + const output = hyperlaneWarpDeployRaw({ + warpCorePath: nonExistingFilePath, + }) + .stdio('pipe') + .nothrow(); + + const finalOutput = await handlePrompts(output, steps); + + expect(finalOutput.exitCode).to.equal(1); + expect( + finalOutput + .text() + .includes(`No "Warp route deployment config" found in`) || + finalOutput + .text() + .includes(`Invalid file format for ${nonExistingFilePath}`), + ).to.be.true; + }); + + it(`should successfully deploy a ${TokenType.collateral} -> ${TokenType.synthetic} warp route`, async function () { + const token = await deployToken(ANVIL_KEY, CHAIN_NAME_2); + + const [expectedTokenSymbol, expectedTokenDecimals] = await Promise.all([ + token.symbol(), + token.decimals(), + ]); + const COMBINED_WARP_CORE_CONFIG_PATH = getCombinedWarpRoutePath( + expectedTokenSymbol, + [CHAIN_NAME_2, CHAIN_NAME_3], + ); + + const warpConfig: WarpRouteDeployConfig = { + [CHAIN_NAME_2]: { + type: TokenType.collateral, + token: token.address, + mailbox: chain2Addresses.mailbox, + owner: ownerAddress, + }, + [CHAIN_NAME_3]: { + type: TokenType.synthetic, + mailbox: chain3Addresses.mailbox, + owner: ownerAddress, + }, + }; + + writeYamlOrJson(WARP_DEPLOY_OUTPUT_PATH, warpConfig); + + const steps: TestPromptAction[] = [ + { + check: (currentOutput) => + currentOutput.includes('Please enter the private key for chain'), + input: `${ANVIL_KEY}${KeyBoardKeys.ENTER}`, + }, + { + check: (currentOutput) => + currentOutput.includes('Please enter the private key for chain'), + input: `${ANVIL_KEY}${KeyBoardKeys.ENTER}`, + }, + { + check: (currentOutput) => + currentOutput.includes('Is this deployment plan correct?'), + input: KeyBoardKeys.ENTER, + }, + ]; + + // Deploy + const output = hyperlaneWarpDeployRaw({ + warpCorePath: WARP_DEPLOY_OUTPUT_PATH, + }) + .stdio('pipe') + .nothrow(); + + const finalOutput = await handlePrompts(output, steps); + + // Assertions + expect(finalOutput.exitCode).to.equal(0); + for (const chainName of [CHAIN_NAME_2, CHAIN_NAME_3]) { + await assertWarpRouteConfig( + warpConfig, + COMBINED_WARP_CORE_CONFIG_PATH, + chainName, + { decimals: expectedTokenDecimals, symbol: expectedTokenSymbol }, + ); + } + }); + }); + + describe('hyperlane warp deploy --config ... --yes', () => { + it(`should exit early when the provided deployment file does not exist and the skip flag is provided`, async function () { + const nonExistingFilePath = 'non-existing-path'; + // Currently if the file provided in the config flag does not exist a prompt will still be shown to the + // user to enter a valid file and then it will finally fail + const steps: TestPromptAction[] = [ + { + check: (currentOutput: string) => + currentOutput.includes('Select Warp route deployment config file'), + input: `${KeyBoardKeys.ARROW_DOWN}${KeyBoardKeys.ENTER}`, + }, + { + check: (currentOutput: string) => + currentOutput.includes( + 'Enter Warp route deployment config filepath', + ), + input: `${nonExistingFilePath}${KeyBoardKeys.ENTER}`, + }, + ]; + + const output = hyperlaneWarpDeployRaw({ + warpCorePath: nonExistingFilePath, + skipConfirmationPrompts: true, + }) + .stdio('pipe') + .nothrow(); + + const finalOutput = await handlePrompts(output, steps); + + expect(finalOutput.exitCode).to.equal(1); + expect(finalOutput.text()).to.include( + `Warp route deployment config is required`, + ); + }); + + it(`should successfully deploy a ${TokenType.collateral} -> ${TokenType.synthetic} warp route`, async function () { + const token = await deployToken(ANVIL_KEY, CHAIN_NAME_2); + + const [expectedTokenSymbol, expectedTokenDecimals] = await Promise.all([ + token.symbol(), + token.decimals(), + ]); + console.log(expectedTokenDecimals); + const COMBINED_WARP_CORE_CONFIG_PATH = getCombinedWarpRoutePath( + expectedTokenSymbol, + [CHAIN_NAME_2, CHAIN_NAME_3], + ); + + const warpConfig: WarpRouteDeployConfig = { + [CHAIN_NAME_2]: { + type: TokenType.collateral, + token: token.address, + mailbox: chain2Addresses.mailbox, + owner: ownerAddress, + }, + [CHAIN_NAME_3]: { + type: TokenType.synthetic, + mailbox: chain3Addresses.mailbox, + owner: ownerAddress, + }, + }; + + writeYamlOrJson(WARP_DEPLOY_OUTPUT_PATH, warpConfig); + + const steps: TestPromptAction[] = [ + { + check: (currentOutput) => + currentOutput.includes('Please enter the private key for chain'), + input: `${ANVIL_KEY}${KeyBoardKeys.ENTER}`, + }, + { + check: (currentOutput) => + currentOutput.includes('Please enter the private key for chain'), + input: `${ANVIL_KEY}${KeyBoardKeys.ENTER}`, + }, + ]; + + // Deploy + const output = hyperlaneWarpDeployRaw({ + warpCorePath: WARP_DEPLOY_OUTPUT_PATH, + skipConfirmationPrompts: true, + }) + .stdio('pipe') + .nothrow(); + + const finalOutput = await handlePrompts(output, steps); + + // Assertions + expect(finalOutput.exitCode).to.equal(0); + + for (const chainName of [CHAIN_NAME_2, CHAIN_NAME_3]) { + await assertWarpRouteConfig( + warpConfig, + COMBINED_WARP_CORE_CONFIG_PATH, + chainName, + { decimals: expectedTokenDecimals, symbol: expectedTokenSymbol }, + ); + } + }); + }); + + describe(`hyperlane warp deploy --config ... --yes --key ...`, () => { + let tokenChain2: ERC20Test; + let tokenChain2Symbol: string; + let vaultChain2: ERC4626Test; + let tokenVaultChain2Symbol: string; + + let tokenChain3: ERC20Test; + let tokenChain3Symbol: string; + let vaultChain3: ERC4626Test; + let tokenVaultChain3Symbol: string; + + let warpConfigTestCases: ReadonlyArray; + + before(async () => { + tokenChain2 = await deployToken(ANVIL_KEY, CHAIN_NAME_2); + vaultChain2 = await deploy4626Vault( + ANVIL_KEY, + CHAIN_NAME_2, + tokenChain2.address, + ); + + [tokenChain2Symbol, tokenVaultChain2Symbol] = await Promise.all([ + tokenChain2.symbol(), + vaultChain2.symbol(), + ]); + + tokenChain3 = await deployToken(ANVIL_KEY, CHAIN_NAME_3); + vaultChain3 = await deploy4626Vault( + ANVIL_KEY, + CHAIN_NAME_3, + tokenChain3.address, + ); + + [tokenChain3Symbol, tokenVaultChain3Symbol] = await Promise.all([ + tokenChain3.symbol(), + vaultChain3.symbol(), + ]); + + warpConfigTestCases = generateWarpConfigs( + { + chainName: CHAIN_NAME_2, + mailbox: chain2Addresses.mailbox, + owner: ownerAddress, + token: tokenChain2.address, + vault: vaultChain2.address, + }, + { + chainName: CHAIN_NAME_3, + mailbox: chain3Addresses.mailbox, + owner: ownerAddress, + token: tokenChain3.address, + vault: vaultChain3.address, + }, + ); + }); + + function getTokenSymbolFromDeployment( + warpConfig: WarpRouteDeployConfig, + ): string { + let symbol: string; + if (warpConfig[CHAIN_NAME_2].type.match(/.*vault.*/i)) { + symbol = tokenVaultChain2Symbol; + } else if (warpConfig[CHAIN_NAME_2].type.match(/.*collateral.*/i)) { + symbol = tokenChain2Symbol; + } else if (warpConfig[CHAIN_NAME_3].type.match(/.*vault.*/i)) { + symbol = tokenVaultChain3Symbol; + } else if (warpConfig[CHAIN_NAME_3].type.match(/.*collateral.*/i)) { + symbol = tokenChain3Symbol; + } else { + symbol = 'ETH'; + } + + return symbol; + } + + async function collateralizeWarpTokens( + routeConfigPath: string, + warpDeployConfig: WarpRouteDeployConfig, + walletAndCollateralByChain: ChainMap<{ + wallet: Wallet; + collateral: ERC20Test; + }>, + ) { + const config: ChainMap = ( + readYamlOrJson(routeConfigPath) as WarpCoreConfig + ).tokens.reduce((acc, curr) => ({ ...acc, [curr.chainName]: curr }), {}); + + await Promise.all( + [CHAIN_NAME_2, CHAIN_NAME_3] + .filter((chainName) => walletAndCollateralByChain[chainName]) + .map(async (chainName) => { + if (warpDeployConfig[chainName].type.match(/.*native/i)) { + const tx = await walletAndCollateralByChain[ + chainName + ].wallet.sendTransaction({ + to: config[chainName].addressOrDenom, + value: 1_000_000_000, + }); + + await tx.wait(); + } + + if ( + !warpDeployConfig[chainName].type.match(/.*synthetic/i) && + warpDeployConfig[chainName].type.match(/.*collateral/i) + ) { + const decimals = await walletAndCollateralByChain[ + chainName + ].collateral.decimals(); + const tx = await walletAndCollateralByChain[ + chainName + ].collateral.transfer( + config[chainName].addressOrDenom, + parseUnits('1', decimals), + ); + + await tx.wait(); + } + }), + ); + } + + it('Should deploy and bridge different types of warp routes:', async function () { + // Timeout increased only for this test because it runs multiple times with different deployment configs + this.timeout(warpConfigTestCases.length * DEFAULT_E2E_TEST_TIMEOUT); + + for (const warpConfig of warpConfigTestCases) { + console.log( + `Should deploy and be able to bridge in a ${warpConfig[CHAIN_NAME_2].type} -> ${warpConfig[CHAIN_NAME_3].type} warp route ...`, + ); + + writeYamlOrJson(WARP_DEPLOY_OUTPUT_PATH, warpConfig); + await hyperlaneWarpDeploy(WARP_DEPLOY_OUTPUT_PATH); + + let startChain, targetChain: string; + if (!warpConfig[CHAIN_NAME_2].type.match(/.*synthetic.*/i)) { + startChain = CHAIN_NAME_2; + targetChain = CHAIN_NAME_3; + } else { + startChain = CHAIN_NAME_3; + targetChain = CHAIN_NAME_2; + } + + const symbol = getTokenSymbolFromDeployment(warpConfig); + + const routeConfigPath = getCombinedWarpRoutePath(symbol, [ + CHAIN_NAME_2, + CHAIN_NAME_3, + ]); + + await collateralizeWarpTokens(routeConfigPath, warpConfig, { + [CHAIN_NAME_2]: { + wallet: walletChain2, + collateral: tokenChain2, + }, + [CHAIN_NAME_3]: { + wallet: walletChain3, + collateral: tokenChain3, + }, + }); + + await sendWarpRouteMessageRoundTrip( + startChain, + targetChain, + routeConfigPath, + ); + + console.log( + `Should deploy and be able to bridge in a ${warpConfig[CHAIN_NAME_2].type} -> ${warpConfig[CHAIN_NAME_3].type} warp route ✅`, + ); + } + }); + + it('should only allow rebasing yield route to be deployed with rebasing synthetic', async function () { + const warpConfig: WarpRouteDeployConfig = { + [CHAIN_NAME_2]: { + type: TokenType.collateralVaultRebase, + token: vaultChain2.address, + mailbox: chain2Addresses.mailbox, + owner: chain2Addresses.mailbox, + }, + [CHAIN_NAME_3]: { + type: TokenType.synthetic, + mailbox: chain2Addresses.mailbox, + owner: chain2Addresses.mailbox, + }, + }; + + writeYamlOrJson(WARP_DEPLOY_OUTPUT_PATH, warpConfig); + await hyperlaneWarpDeploy(WARP_DEPLOY_OUTPUT_PATH).should.be.rejected; // TODO: revisit this to figure out how to parse the error. + }); + + it('should deploy with an ISM config', async () => { + // 1. Define ISM configuration + const ism: IsmConfig = { + type: IsmType.MESSAGE_ID_MULTISIG, + validators: [chain2Addresses.mailbox], // Using mailbox address as example validator + threshold: 1, + }; + + // 2. Create Warp configuration with ISM + const warpConfig: WarpRouteDeployConfig = { + [CHAIN_NAME_2]: { + type: TokenType.collateralVaultRebase, + token: vaultChain2.address, + mailbox: chain2Addresses.mailbox, + owner: chain2Addresses.mailbox, + interchainSecurityModule: ism, // Add ISM config here + }, + [CHAIN_NAME_3]: { + type: TokenType.syntheticRebase, + mailbox: chain3Addresses.mailbox, + owner: chain3Addresses.mailbox, + collateralChainName: CHAIN_NAME_2, + }, + }; + + // 3. Write config and deploy + writeYamlOrJson(WARP_DEPLOY_OUTPUT_PATH, warpConfig); + await hyperlaneWarpDeploy(WARP_DEPLOY_OUTPUT_PATH); + + // 4. Verify deployed ISM configuration + const collateralRebaseConfig = ( + await readWarpConfig( + CHAIN_NAME_2, + WARP_CORE_CONFIG_PATH_2_3, + WARP_DEPLOY_OUTPUT_PATH, + ) + )[CHAIN_NAME_2]; + + expect( + normalizeConfig(collateralRebaseConfig.interchainSecurityModule), + ).to.deep.equal(normalizeConfig(ism)); + }); + + it('should deploy with a hook config', async () => { + const hook: HookConfig = { + type: HookType.PROTOCOL_FEE, + beneficiary: chain2Addresses.mailbox, + owner: chain2Addresses.mailbox, + maxProtocolFee: '1337', + protocolFee: '1337', + }; + const warpConfig: WarpRouteDeployConfig = { + [CHAIN_NAME_2]: { + type: TokenType.collateralVaultRebase, + token: vaultChain2.address, + mailbox: chain2Addresses.mailbox, + owner: chain2Addresses.mailbox, + hook, + }, + [CHAIN_NAME_3]: { + type: TokenType.syntheticRebase, + mailbox: chain3Addresses.mailbox, + owner: chain3Addresses.mailbox, + collateralChainName: CHAIN_NAME_2, + }, + }; + + writeYamlOrJson(WARP_DEPLOY_OUTPUT_PATH, warpConfig); + await hyperlaneWarpDeploy(WARP_DEPLOY_OUTPUT_PATH); + + // Check collateralRebase + const collateralRebaseConfig = ( + await readWarpConfig( + CHAIN_NAME_2, + WARP_CORE_CONFIG_PATH_2_3, + WARP_DEPLOY_OUTPUT_PATH, + ) + )[CHAIN_NAME_2]; + + expect(normalizeConfig(collateralRebaseConfig.hook)).to.deep.equal( + normalizeConfig(hook), + ); + }); + + it('should send a message from origin to destination in the correct order', async function () { + const warpConfig: WarpRouteDeployConfig = { + [CHAIN_NAME_2]: { + type: TokenType.collateralVaultRebase, + token: vaultChain2.address, + mailbox: chain2Addresses.mailbox, + owner: chain2Addresses.mailbox, + }, + [CHAIN_NAME_3]: { + type: TokenType.syntheticRebase, + mailbox: chain3Addresses.mailbox, + owner: chain3Addresses.mailbox, + collateralChainName: CHAIN_NAME_2, + }, + }; + + writeYamlOrJson(WARP_DEPLOY_OUTPUT_PATH, warpConfig); + await hyperlaneWarpDeploy(WARP_DEPLOY_OUTPUT_PATH); + + // Try to send a transaction with the origin destination + const { stdout: chain2Tochain3Stdout } = await hyperlaneWarpSendRelay( + CHAIN_NAME_2, + CHAIN_NAME_3, + WARP_CORE_CONFIG_PATH_2_3, + ); + expect(chain2Tochain3Stdout).to.include('anvil2 ➡️ anvil3'); + + // Send another message with swapped origin destination + const { stdout: chain3Tochain2Stdout } = await hyperlaneWarpSendRelay( + CHAIN_NAME_3, + CHAIN_NAME_2, + WARP_CORE_CONFIG_PATH_2_3, + ); + expect(chain3Tochain2Stdout).to.include('anvil3 ➡️ anvil2'); + + // Should throw if invalid origin or destination + await hyperlaneWarpSendRelay( + 'anvil1', + CHAIN_NAME_3, + WARP_CORE_CONFIG_PATH_2_3, + ).should.be.rejectedWith( + 'Error: Origin (anvil1) or destination (anvil3) are not part of the warp route.', + ); + }); + }); +}); diff --git a/typescript/cli/src/tests/warp-init.e2e-test.ts b/typescript/cli/src/tests/warp/warp-init.e2e-test.ts similarity index 97% rename from typescript/cli/src/tests/warp-init.e2e-test.ts rename to typescript/cli/src/tests/warp/warp-init.e2e-test.ts index 66cfa3bfc7..4cdc4a6eec 100644 --- a/typescript/cli/src/tests/warp-init.e2e-test.ts +++ b/typescript/cli/src/tests/warp/warp-init.e2e-test.ts @@ -10,8 +10,7 @@ import { } from '@hyperlane-xyz/sdk'; import { Address } from '@hyperlane-xyz/utils'; -import { readYamlOrJson } from '../utils/files.js'; - +import { readYamlOrJson } from '../../utils/files.js'; import { ANVIL_KEY, CHAIN_NAME_2, @@ -28,8 +27,8 @@ import { deployOrUseExistingCore, deployToken, handlePrompts, -} from './commands/helpers.js'; -import { hyperlaneWarpInit } from './commands/warp.js'; +} from '../commands/helpers.js'; +import { hyperlaneWarpInit } from '../commands/warp.js'; describe('hyperlane warp init e2e tests', async function () { this.timeout(2 * DEFAULT_E2E_TEST_TIMEOUT); diff --git a/typescript/cli/src/tests/warp/warp-read.e2e-test.ts b/typescript/cli/src/tests/warp/warp-read.e2e-test.ts new file mode 100644 index 0000000000..36546cc2c3 --- /dev/null +++ b/typescript/cli/src/tests/warp/warp-read.e2e-test.ts @@ -0,0 +1,171 @@ +import { expect } from 'chai'; +import { Wallet } from 'ethers'; + +import { ChainAddresses } from '@hyperlane-xyz/registry'; +import { TokenType, WarpRouteDeployConfig } from '@hyperlane-xyz/sdk'; +import { Address } from '@hyperlane-xyz/utils'; + +import { readYamlOrJson, writeYamlOrJson } from '../../utils/files.js'; +import { + ANVIL_KEY, + CHAIN_NAME_2, + CHAIN_NAME_3, + CORE_CONFIG_PATH, + DEFAULT_E2E_TEST_TIMEOUT, + KeyBoardKeys, + TestPromptAction, + WARP_CONFIG_PATH_2, + WARP_CONFIG_PATH_EXAMPLE, + WARP_CORE_CONFIG_PATH_2, + WARP_DEPLOY_OUTPUT_PATH, + deployOrUseExistingCore, + handlePrompts, +} from '../commands/helpers.js'; +import { + hyperlaneWarpDeploy, + hyperlaneWarpReadRaw, + readWarpConfig, +} from '../commands/warp.js'; + +describe('hyperlane warp read e2e tests', async function () { + this.timeout(DEFAULT_E2E_TEST_TIMEOUT); + + let anvil2Config: WarpRouteDeployConfig; + + let chain2Addresses: ChainAddresses = {}; + let chain3Addresses: ChainAddresses = {}; + + let ownerAddress: Address; + + before(async function () { + [chain2Addresses, chain3Addresses] = await Promise.all([ + deployOrUseExistingCore(CHAIN_NAME_2, CORE_CONFIG_PATH, ANVIL_KEY), + deployOrUseExistingCore(CHAIN_NAME_3, CORE_CONFIG_PATH, ANVIL_KEY), + ]); + + ownerAddress = new Wallet(ANVIL_KEY).address; + }); + + before(async function () { + await deployOrUseExistingCore(CHAIN_NAME_2, CORE_CONFIG_PATH, ANVIL_KEY); + + // Create a new warp config using the example + const exampleWarpConfig: WarpRouteDeployConfig = readYamlOrJson( + WARP_CONFIG_PATH_EXAMPLE, + ); + anvil2Config = { [CHAIN_NAME_2]: { ...exampleWarpConfig.anvil1 } }; + writeYamlOrJson(WARP_CONFIG_PATH_2, anvil2Config); + }); + + describe('hyperlane warp read --key ... --config ...', () => { + it('should exit early if no symbol, chain or warp file have been provided', async () => { + await hyperlaneWarpDeploy(WARP_CONFIG_PATH_2); + + const output = await hyperlaneWarpReadRaw({ + privateKey: ANVIL_KEY, + outputPath: WARP_CONFIG_PATH_2, + }).nothrow(); + + expect(output.exitCode).to.equal(1); + expect(output.text()).to.include( + 'Please specify either a symbol, chain and address or warp file', + ); + }); + }); + + describe('hyperlane warp read --config ... --symbol ...', () => { + it('should successfully read the complete warp route config from all the chains', async () => { + await hyperlaneWarpDeploy(WARP_CONFIG_PATH_2); + + const steps: TestPromptAction[] = [ + { + check: (currentOutput) => + currentOutput.includes('Please enter the private key for chain'), + input: `${ANVIL_KEY}${KeyBoardKeys.ENTER}`, + }, + ]; + + const output = hyperlaneWarpReadRaw({ + symbol: 'ETH', + outputPath: WARP_CONFIG_PATH_2, + }) + .stdio('pipe') + .nothrow(); + + const finalOutput = await handlePrompts(output, steps); + + expect(finalOutput.exitCode).to.equal(0); + + const warpReadResult: WarpRouteDeployConfig = + readYamlOrJson(WARP_CONFIG_PATH_2); + expect(warpReadResult[CHAIN_NAME_2]).not.to.be.undefined; + expect(warpReadResult[CHAIN_NAME_2].type).to.equal(TokenType.native); + }); + }); + + describe('hyperlane warp read --key ... --symbol ...', () => { + it('should successfully read the complete warp route config from all the chains', async () => { + const warpConfig: WarpRouteDeployConfig = { + [CHAIN_NAME_2]: { + type: TokenType.native, + mailbox: chain2Addresses.mailbox, + owner: ownerAddress, + }, + [CHAIN_NAME_3]: { + type: TokenType.synthetic, + mailbox: chain3Addresses.mailbox, + owner: ownerAddress, + }, + }; + + writeYamlOrJson(WARP_DEPLOY_OUTPUT_PATH, warpConfig); + await hyperlaneWarpDeploy(WARP_DEPLOY_OUTPUT_PATH); + + const steps: TestPromptAction[] = [ + // Select the anvil2-anvil3 ETH route from the selection prompt + { + check: (currentOutput: string) => + currentOutput.includes('Select from matching warp routes'), + input: KeyBoardKeys.ENTER, + }, + ]; + + const output = hyperlaneWarpReadRaw({ + privateKey: ANVIL_KEY, + symbol: 'ETH', + outputPath: WARP_DEPLOY_OUTPUT_PATH, + }) + .stdio('pipe') + .nothrow(); + + const finalOutput = await handlePrompts(output, steps); + + expect(finalOutput.exitCode).to.equal(0); + + const warpReadResult: WarpRouteDeployConfig = readYamlOrJson( + WARP_DEPLOY_OUTPUT_PATH, + ); + expect(warpReadResult[CHAIN_NAME_2]).not.to.be.undefined; + expect(warpReadResult[CHAIN_NAME_2].type).to.equal(TokenType.native); + + expect(warpReadResult[CHAIN_NAME_3]).not.to.be.undefined; + expect(warpReadResult[CHAIN_NAME_3].type).to.equal(TokenType.synthetic); + }); + }); + + describe('hyperlane warp read --key ... --chain ... --config ...', () => { + it('should be able to read a warp route', async function () { + await hyperlaneWarpDeploy(WARP_CONFIG_PATH_2); + + const warpReadResult: WarpRouteDeployConfig = await readWarpConfig( + CHAIN_NAME_2, + WARP_CORE_CONFIG_PATH_2, + WARP_DEPLOY_OUTPUT_PATH, + ); + + expect(warpReadResult[CHAIN_NAME_2].type).to.be.equal( + anvil2Config[CHAIN_NAME_2].type, + ); + }); + }); +}); diff --git a/typescript/cli/src/tests/warp/warp-send.e2e-test.ts b/typescript/cli/src/tests/warp/warp-send.e2e-test.ts new file mode 100644 index 0000000000..9974d59f41 --- /dev/null +++ b/typescript/cli/src/tests/warp/warp-send.e2e-test.ts @@ -0,0 +1,400 @@ +import { JsonRpcProvider } from '@ethersproject/providers'; +import { expect } from 'chai'; +import { Wallet } from 'ethers'; +import { parseEther } from 'ethers/lib/utils.js'; + +import { ERC20__factory } from '@hyperlane-xyz/core'; +import { ChainAddresses } from '@hyperlane-xyz/registry'; +import { + ChainMap, + ChainMetadata, + Token, + TokenType, + WarpCoreConfig, + WarpRouteDeployConfig, +} from '@hyperlane-xyz/sdk'; +import { Address } from '@hyperlane-xyz/utils'; + +import { WarpSendLogs } from '../../send/transfer.js'; +import { readYamlOrJson, writeYamlOrJson } from '../../utils/files.js'; +import { + ANVIL_KEY, + CHAIN_2_METADATA_PATH, + CHAIN_3_METADATA_PATH, + CHAIN_NAME_2, + CHAIN_NAME_3, + CORE_CONFIG_PATH, + DEFAULT_E2E_TEST_TIMEOUT, + WARP_DEPLOY_OUTPUT_PATH, + deployOrUseExistingCore, + deployToken, + getCombinedWarpRoutePath, +} from '../commands/helpers.js'; +import { + hyperlaneWarpDeploy, + hyperlaneWarpSendRelay, +} from '../commands/warp.js'; + +describe('hyperlane warp deploy e2e tests', async function () { + this.timeout(DEFAULT_E2E_TEST_TIMEOUT); + + let chain2Addresses: ChainAddresses = {}; + let chain3Addresses: ChainAddresses = {}; + + let ownerAddress: Address; + let walletChain2: Wallet; + let walletChain3: Wallet; + + before(async function () { + [chain2Addresses, chain3Addresses] = await Promise.all([ + deployOrUseExistingCore(CHAIN_NAME_2, CORE_CONFIG_PATH, ANVIL_KEY), + deployOrUseExistingCore(CHAIN_NAME_3, CORE_CONFIG_PATH, ANVIL_KEY), + ]); + + const chain2Metadata: ChainMetadata = readYamlOrJson(CHAIN_2_METADATA_PATH); + const chain3Metadata: ChainMetadata = readYamlOrJson(CHAIN_3_METADATA_PATH); + + const providerChain2 = new JsonRpcProvider(chain2Metadata.rpcUrls[0].http); + const providerChain3 = new JsonRpcProvider(chain3Metadata.rpcUrls[0].http); + + walletChain2 = new Wallet(ANVIL_KEY).connect(providerChain2); + walletChain3 = new Wallet(ANVIL_KEY).connect(providerChain3); + ownerAddress = walletChain2.address; + }); + + it(`should be able to bridge between ${TokenType.collateral} and ${TokenType.synthetic}`, async function () { + const token = await deployToken(ANVIL_KEY, CHAIN_NAME_2); + const tokenSymbol = await token.symbol(); + + const WARP_CORE_CONFIG_PATH_2_3 = getCombinedWarpRoutePath(tokenSymbol, [ + CHAIN_NAME_2, + CHAIN_NAME_3, + ]); + + const warpConfig: WarpRouteDeployConfig = { + [CHAIN_NAME_2]: { + type: TokenType.collateral, + token: token.address, + mailbox: chain2Addresses.mailbox, + owner: ownerAddress, + }, + [CHAIN_NAME_3]: { + type: TokenType.synthetic, + mailbox: chain3Addresses.mailbox, + owner: ownerAddress, + }, + }; + + writeYamlOrJson(WARP_DEPLOY_OUTPUT_PATH, warpConfig); + await hyperlaneWarpDeploy(WARP_DEPLOY_OUTPUT_PATH); + + const config: ChainMap = ( + readYamlOrJson(WARP_CORE_CONFIG_PATH_2_3) as WarpCoreConfig + ).tokens.reduce((acc, curr) => ({ ...acc, [curr.chainName]: curr }), {}); + const synthetic = ERC20__factory.connect( + config[CHAIN_NAME_3].addressOrDenom, + walletChain3, + ); + + const [tokenBalanceOnChain2Before, tokenBalanceOnChain3Before] = + await Promise.all([ + token.callStatic.balanceOf(walletChain2.address), + synthetic.callStatic.balanceOf(walletChain3.address), + ]); + + const { stdout, exitCode } = await hyperlaneWarpSendRelay( + CHAIN_NAME_2, + CHAIN_NAME_3, + WARP_CORE_CONFIG_PATH_2_3, + ); + expect(exitCode).to.equal(0); + expect(stdout).to.include(WarpSendLogs.SUCCESS); + + const [tokenBalanceOnChain2After, tokenBalanceOnChain3After] = + await Promise.all([ + token.callStatic.balanceOf(walletChain2.address), + synthetic.callStatic.balanceOf(walletChain3.address), + ]); + + expect(tokenBalanceOnChain2After.lt(tokenBalanceOnChain2Before)).to.be.true; + expect(tokenBalanceOnChain3After.gt(tokenBalanceOnChain3Before)).to.be.true; + }); + + it(`should be able to bridge between ${TokenType.collateral} and ${TokenType.collateral}`, async function () { + const [tokenChain2, tokenChain3] = await Promise.all([ + deployToken(ANVIL_KEY, CHAIN_NAME_2), + deployToken(ANVIL_KEY, CHAIN_NAME_3), + ]); + const tokenSymbolChain2 = await tokenChain2.symbol(); + + const WARP_CORE_CONFIG_PATH_2_3 = getCombinedWarpRoutePath( + tokenSymbolChain2, + [CHAIN_NAME_2, CHAIN_NAME_3], + ); + + const warpConfig: WarpRouteDeployConfig = { + [CHAIN_NAME_2]: { + type: TokenType.collateral, + token: tokenChain2.address, + mailbox: chain2Addresses.mailbox, + owner: ownerAddress, + }, + [CHAIN_NAME_3]: { + type: TokenType.collateral, + mailbox: chain3Addresses.mailbox, + token: tokenChain3.address, + owner: ownerAddress, + }, + }; + + writeYamlOrJson(WARP_DEPLOY_OUTPUT_PATH, warpConfig); + await hyperlaneWarpDeploy(WARP_DEPLOY_OUTPUT_PATH); + + const config: ChainMap = ( + readYamlOrJson(WARP_CORE_CONFIG_PATH_2_3) as WarpCoreConfig + ).tokens.reduce((acc, curr) => ({ ...acc, [curr.chainName]: curr }), {}); + const collateral = parseEther('1'); + const tx = await tokenChain3.transfer( + config[CHAIN_NAME_3].addressOrDenom, + collateral, + ); + await tx.wait(); + + const [tokenBalanceOnChain2Before, tokenBalanceOnChain3Before] = + await Promise.all([ + tokenChain2.callStatic.balanceOf(walletChain2.address), + tokenChain3.callStatic.balanceOf(walletChain3.address), + ]); + + const { stdout } = await hyperlaneWarpSendRelay( + CHAIN_NAME_2, + CHAIN_NAME_3, + WARP_CORE_CONFIG_PATH_2_3, + undefined, + Number(collateral), + ); + expect(stdout).to.include(WarpSendLogs.SUCCESS); + + const [tokenBalanceOnChain2After, tokenBalanceOnChain3After] = + await Promise.all([ + tokenChain2.callStatic.balanceOf(walletChain2.address), + tokenChain3.callStatic.balanceOf(walletChain3.address), + ]); + + expect(tokenBalanceOnChain2After.lt(tokenBalanceOnChain2Before)).to.be.true; + expect(tokenBalanceOnChain3After.gt(tokenBalanceOnChain3Before)).to.be.true; + }); + + it(`should be able to bridge between ${TokenType.native} and ${TokenType.synthetic}`, async function () { + const WARP_CORE_CONFIG_PATH_2_3 = getCombinedWarpRoutePath('ETH', [ + CHAIN_NAME_2, + CHAIN_NAME_3, + ]); + + const warpConfig: WarpRouteDeployConfig = { + [CHAIN_NAME_2]: { + type: TokenType.native, + mailbox: chain2Addresses.mailbox, + owner: ownerAddress, + }, + [CHAIN_NAME_3]: { + type: TokenType.synthetic, + mailbox: chain3Addresses.mailbox, + owner: ownerAddress, + }, + }; + + writeYamlOrJson(WARP_DEPLOY_OUTPUT_PATH, warpConfig); + await hyperlaneWarpDeploy(WARP_DEPLOY_OUTPUT_PATH); + + const config: ChainMap = ( + readYamlOrJson(WARP_CORE_CONFIG_PATH_2_3) as WarpCoreConfig + ).tokens.reduce((acc, curr) => ({ ...acc, [curr.chainName]: curr }), {}); + + const synthetic = ERC20__factory.connect( + config[CHAIN_NAME_3].addressOrDenom, + walletChain3, + ); + const [nativeBalanceOnChain2Before, syntheticBalanceOnChain3Before] = + await Promise.all([ + walletChain2.getBalance(), + synthetic.callStatic.balanceOf(walletChain3.address), + ]); + + const { stdout, exitCode } = await hyperlaneWarpSendRelay( + CHAIN_NAME_2, + CHAIN_NAME_3, + WARP_CORE_CONFIG_PATH_2_3, + ); + + expect(exitCode).to.equal(0); + expect(stdout).to.include(WarpSendLogs.SUCCESS); + + const [nativeBalanceOnChain2After, syntheticBalanceOnChain3After] = + await Promise.all([ + walletChain2.getBalance(), + synthetic.callStatic.balanceOf(walletChain3.address), + ]); + + expect(nativeBalanceOnChain2After.lt(nativeBalanceOnChain2Before)).to.be + .true; + expect(syntheticBalanceOnChain3After.gt(syntheticBalanceOnChain3Before)).to + .be.true; + }); + + it(`should be able to bridge between ${TokenType.native} and ${TokenType.native}`, async function () { + const WARP_CORE_CONFIG_PATH_2_3 = getCombinedWarpRoutePath('ETH', [ + CHAIN_NAME_2, + CHAIN_NAME_3, + ]); + + const warpConfig: WarpRouteDeployConfig = { + [CHAIN_NAME_2]: { + type: TokenType.native, + mailbox: chain2Addresses.mailbox, + owner: ownerAddress, + }, + [CHAIN_NAME_3]: { + type: TokenType.native, + mailbox: chain3Addresses.mailbox, + owner: ownerAddress, + }, + }; + + writeYamlOrJson(WARP_DEPLOY_OUTPUT_PATH, warpConfig); + await hyperlaneWarpDeploy(WARP_DEPLOY_OUTPUT_PATH); + + const config: ChainMap = ( + readYamlOrJson(WARP_CORE_CONFIG_PATH_2_3) as WarpCoreConfig + ).tokens.reduce((acc, curr) => ({ ...acc, [curr.chainName]: curr }), {}); + + const collateral = parseEther('1'); + // Sending eth to the hypnative contract otherwise bridging will fail + await walletChain3.sendTransaction({ + to: config[CHAIN_NAME_3].addressOrDenom, + value: collateral, + }); + + const [nativeBalanceOnChain2Before, nativeBalanceOnChain3Before] = + await Promise.all([walletChain2.getBalance(), walletChain3.getBalance()]); + + const { stdout, exitCode } = await hyperlaneWarpSendRelay( + CHAIN_NAME_2, + CHAIN_NAME_3, + WARP_CORE_CONFIG_PATH_2_3, + undefined, + Number(collateral), + ); + + expect(exitCode).to.equal(0); + expect(stdout).to.include(WarpSendLogs.SUCCESS); + + const [nativeBalanceOnChain2After, nativeBalanceOnChain3After] = + await Promise.all([walletChain2.getBalance(), walletChain3.getBalance()]); + + expect(nativeBalanceOnChain2After.lt(nativeBalanceOnChain2Before)).to.be + .true; + expect(nativeBalanceOnChain3After.gt(nativeBalanceOnChain3Before)).to.be + .true; + }); + + it(`should not be able to bridge between ${TokenType.native} and ${TokenType.native} when the token on the destination chain does not have enough collateral`, async function () { + const WARP_CORE_CONFIG_PATH_2_3 = getCombinedWarpRoutePath('ETH', [ + CHAIN_NAME_2, + CHAIN_NAME_3, + ]); + + const warpConfig: WarpRouteDeployConfig = { + [CHAIN_NAME_2]: { + type: TokenType.native, + mailbox: chain2Addresses.mailbox, + owner: ownerAddress, + }, + [CHAIN_NAME_3]: { + type: TokenType.native, + mailbox: chain3Addresses.mailbox, + owner: ownerAddress, + }, + }; + + writeYamlOrJson(WARP_DEPLOY_OUTPUT_PATH, warpConfig); + await hyperlaneWarpDeploy(WARP_DEPLOY_OUTPUT_PATH); + + const [nativeBalanceOnChain1Before, nativeBalanceOnChain2Before] = + await Promise.all([walletChain2.getBalance(), walletChain3.getBalance()]); + + const { exitCode, stdout } = await hyperlaneWarpSendRelay( + CHAIN_NAME_2, + CHAIN_NAME_3, + WARP_CORE_CONFIG_PATH_2_3, + undefined, + Number(parseEther('1')), + ).nothrow(); + + expect(exitCode).to.equal(1); + expect(stdout).to.include(`to ${CHAIN_NAME_3} has INSUFFICIENT collateral`); + + const [nativeBalanceOnChain1After, nativeBalanceOnChain2After] = + await Promise.all([walletChain2.getBalance(), walletChain3.getBalance()]); + + expect(nativeBalanceOnChain1After.eq(nativeBalanceOnChain1Before)).to.be + .true; + expect(nativeBalanceOnChain2After.eq(nativeBalanceOnChain2Before)).to.be + .true; + }); + + it(`should not be able to bridge between ${TokenType.collateral} and ${TokenType.collateral} when the token on the destination chain does not have enough collateral`, async function () { + const [tokenChain2, tokenChain3] = await Promise.all([ + deployToken(ANVIL_KEY, CHAIN_NAME_2), + deployToken(ANVIL_KEY, CHAIN_NAME_3), + ]); + const tokenSymbolChain2 = await tokenChain2.symbol(); + + const WARP_CORE_CONFIG_PATH_2_3 = getCombinedWarpRoutePath( + tokenSymbolChain2, + [CHAIN_NAME_2, CHAIN_NAME_3], + ); + + const warpConfig: WarpRouteDeployConfig = { + [CHAIN_NAME_2]: { + type: TokenType.collateral, + token: tokenChain2.address, + mailbox: chain2Addresses.mailbox, + owner: ownerAddress, + }, + [CHAIN_NAME_3]: { + type: TokenType.collateral, + mailbox: chain3Addresses.mailbox, + token: tokenChain3.address, + owner: ownerAddress, + }, + }; + + writeYamlOrJson(WARP_DEPLOY_OUTPUT_PATH, warpConfig); + await hyperlaneWarpDeploy(WARP_DEPLOY_OUTPUT_PATH); + + const [tokenBalanceOnChain2Before, tokenBalanceOnChain3Before] = + await Promise.all([ + tokenChain2.callStatic.balanceOf(walletChain2.address), + tokenChain3.callStatic.balanceOf(walletChain3.address), + ]); + + const { exitCode, stdout } = await hyperlaneWarpSendRelay( + CHAIN_NAME_2, + CHAIN_NAME_3, + WARP_CORE_CONFIG_PATH_2_3, + ).nothrow(); + + expect(exitCode).to.equal(1); + expect(stdout).to.include(`to ${CHAIN_NAME_3} has INSUFFICIENT collateral`); + + const [tokenBalanceOnChain2After, tokenBalanceOnChain3After] = + await Promise.all([ + tokenChain2.callStatic.balanceOf(walletChain2.address), + tokenChain3.callStatic.balanceOf(walletChain3.address), + ]); + + expect(tokenBalanceOnChain2After.eq(tokenBalanceOnChain2Before)).to.be.true; + expect(tokenBalanceOnChain3After.eq(tokenBalanceOnChain3Before)).to.be.true; + }); +}); diff --git a/typescript/cli/src/version.ts b/typescript/cli/src/version.ts index 047e32b00a..29ec3c951a 100644 --- a/typescript/cli/src/version.ts +++ b/typescript/cli/src/version.ts @@ -1 +1 @@ -export const VERSION = '8.2.0'; +export const VERSION = '8.3.0'; diff --git a/typescript/github-proxy/CHANGELOG.md b/typescript/github-proxy/CHANGELOG.md index 541c2f54f7..f8301f73b1 100644 --- a/typescript/github-proxy/CHANGELOG.md +++ b/typescript/github-proxy/CHANGELOG.md @@ -1,5 +1,7 @@ # @hyperlane-xyz/github-proxy +## 8.3.0 + ## 8.2.0 ## 8.1.0 diff --git a/typescript/github-proxy/package.json b/typescript/github-proxy/package.json index f729b28c4c..b3b2884f18 100644 --- a/typescript/github-proxy/package.json +++ b/typescript/github-proxy/package.json @@ -1,7 +1,7 @@ { "name": "@hyperlane-xyz/github-proxy", "description": "Github proxy that adds the API key to requests", - "version": "8.2.0", + "version": "8.3.0", "private": true, "scripts": { "deploy": "wrangler deploy", diff --git a/typescript/helloworld/CHANGELOG.md b/typescript/helloworld/CHANGELOG.md index b062c87d2f..9ff643af30 100644 --- a/typescript/helloworld/CHANGELOG.md +++ b/typescript/helloworld/CHANGELOG.md @@ -1,5 +1,16 @@ # @hyperlane-xyz/helloworld +## 8.3.0 + +### Patch Changes + +- Updated dependencies [db8c09011] +- Updated dependencies [7546c0181] +- Updated dependencies [11cf66c5e] +- Updated dependencies [49856fbb9] + - @hyperlane-xyz/core@5.10.0 + - @hyperlane-xyz/sdk@8.3.0 + ## 8.2.0 ### Patch Changes diff --git a/typescript/helloworld/package.json b/typescript/helloworld/package.json index c95e09fa9f..8a25b053d7 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": "8.2.0", + "version": "8.3.0", "dependencies": { - "@hyperlane-xyz/core": "5.9.2", + "@hyperlane-xyz/core": "5.10.0", "@hyperlane-xyz/registry": "7.1.0", - "@hyperlane-xyz/sdk": "8.2.0", + "@hyperlane-xyz/sdk": "8.3.0", "@openzeppelin/contracts-upgradeable": "^4.9.3", "ethers": "^5.7.2" }, diff --git a/typescript/infra/CHANGELOG.md b/typescript/infra/CHANGELOG.md index a7810b698b..694d8750e1 100644 --- a/typescript/infra/CHANGELOG.md +++ b/typescript/infra/CHANGELOG.md @@ -1,5 +1,20 @@ # @hyperlane-xyz/infra +## 8.3.0 + +### Minor Changes + +- fed42c325: updated zero ETH warp route config getter +- 31c89a3c1: Add support for Artela config + +### Patch Changes + +- Updated dependencies [7546c0181] +- Updated dependencies [49856fbb9] + - @hyperlane-xyz/sdk@8.3.0 + - @hyperlane-xyz/helloworld@8.3.0 + - @hyperlane-xyz/utils@8.3.0 + ## 8.2.0 ### Minor Changes diff --git a/typescript/infra/config/environments/mainnet3/agent.ts b/typescript/infra/config/environments/mainnet3/agent.ts index c740beb354..7745324c4b 100644 --- a/typescript/infra/config/environments/mainnet3/agent.ts +++ b/typescript/infra/config/environments/mainnet3/agent.ts @@ -640,8 +640,9 @@ const blacklistedMessageIds = [ // USDC/ethereum-inevm '0x998746dc822dc15332b8683fb8a29aec22ed3e2f2fb8245c40f56303c5cb6032', - // malformed recipient in a warp transfer to `treasure` + // malformed recipient in warp transfers '0xf20e3dc5172d824b146b91bb33d66532915fab605e44d2d76af7b5898a6390fe', + '0xd4254c0a44ac41f554ebcbb4eff5efd8a9063747e67f9ca4a57ad232e7c8e267', ]; // Blacklist matching list intended to be used by all contexts. @@ -658,7 +659,7 @@ const hyperlane: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: '62702d3-20250121-002648', + tag: '09e1d5b-20250121-214732', }, blacklist, gasPaymentEnforcement: gasPaymentEnforcement, @@ -678,7 +679,7 @@ const hyperlane: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: '95deca3-20250120-103609', + tag: '359ce5d-20250121-133827', }, resources: scraperResources, }, @@ -693,7 +694,7 @@ const releaseCandidate: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: '7546c01-20250120-171540', + tag: '09e1d5b-20250121-214732', }, blacklist, // We're temporarily (ab)using the RC relayer as a way to increase @@ -727,7 +728,7 @@ const neutron: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: '234704d-20241226-192528', + tag: '09e1d5b-20250121-214732', }, blacklist, gasPaymentEnforcement, diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getBaseSolanaTRUMPWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getBaseSolanaTRUMPWarpConfig.ts index e396d78e83..46f0258c09 100644 --- a/typescript/infra/config/environments/mainnet3/warp/configGetters/getBaseSolanaTRUMPWarpConfig.ts +++ b/typescript/infra/config/environments/mainnet3/warp/configGetters/getBaseSolanaTRUMPWarpConfig.ts @@ -22,7 +22,6 @@ export const getTrumpchainTRUMPWarpConfig = async ( type: TokenType.collateral, name, symbol, - decimals: 6, totalSupply, token: '6p6xgHyF7AeE6TZkSmFsko444wqoP15icUSqi2jfGiPN', owner: DEPLOYER, @@ -65,7 +64,6 @@ export const getTRUMPWarpConfig = async ( type: TokenType.collateral, name, symbol, - decimals: 6, totalSupply, token: '6p6xgHyF7AeE6TZkSmFsko444wqoP15icUSqi2jfGiPN', owner: DEPLOYER, @@ -86,9 +84,9 @@ export const getTRUMPWarpConfig = async ( ...routerConfig.arbitrum, ...syntheticToken, type: TokenType.synthetic, - owner: DEPLOYER, + owner: abacusWorksEnvOwnerConfig.arbitrum.owner, proxyAdmin: { - owner: DEPLOYER, + owner: abacusWorksEnvOwnerConfig.arbitrum.owner, address: '0x2350389Ea8649Da5dD4Fdd09c414dD8463C2695c', }, }, @@ -96,9 +94,9 @@ export const getTRUMPWarpConfig = async ( ...routerConfig.avalanche, ...syntheticToken, type: TokenType.synthetic, - owner: DEPLOYER, + owner: abacusWorksEnvOwnerConfig.avalanche.owner, proxyAdmin: { - owner: DEPLOYER, + owner: abacusWorksEnvOwnerConfig.avalanche.owner, address: '0x86a2E32BB42584127a24079a4f9113EeFE80db90', }, }, @@ -106,9 +104,9 @@ export const getTRUMPWarpConfig = async ( ...routerConfig.flowmainnet, ...syntheticToken, type: TokenType.synthetic, - owner: DEPLOYER, + owner: abacusWorksEnvOwnerConfig.flowmainnet.owner, proxyAdmin: { - owner: DEPLOYER, + owner: abacusWorksEnvOwnerConfig.flowmainnet.owner, address: '0xB504EA900302C7Faf24Cc4F155006d6c0357Dc35', }, }, @@ -116,9 +114,9 @@ export const getTRUMPWarpConfig = async ( ...routerConfig.form, ...syntheticToken, type: TokenType.synthetic, - owner: DEPLOYER, + owner: abacusWorksEnvOwnerConfig.form.owner, proxyAdmin: { - owner: DEPLOYER, + owner: abacusWorksEnvOwnerConfig.form.owner, address: '0x5b3EeADcc0E2d4284eA6816e2E503c24d30a9E54', }, }, @@ -126,9 +124,9 @@ export const getTRUMPWarpConfig = async ( ...routerConfig.worldchain, ...syntheticToken, type: TokenType.synthetic, - owner: DEPLOYER, + owner: abacusWorksEnvOwnerConfig.worldchain.owner, proxyAdmin: { - owner: DEPLOYER, + owner: abacusWorksEnvOwnerConfig.worldchain.owner, address: '0x97e4682dBC4Bfd432F1563a7fa9aC218Bc48c861', }, }, @@ -136,9 +134,9 @@ export const getTRUMPWarpConfig = async ( ...routerConfig.optimism, ...syntheticToken, type: TokenType.synthetic, - owner: DEPLOYER, + owner: abacusWorksEnvOwnerConfig.optimism.owner, proxyAdmin: { - owner: DEPLOYER, + owner: abacusWorksEnvOwnerConfig.optimism.owner, address: '0x6Fa52E2Fc86B200e7b80394e226929C1f9Ff2950', }, }, diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getBaseSolanamainnetTONYWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getBaseSolanamainnetTONYWarpConfig.ts index dc16268fa7..6e51bcad6a 100644 --- a/typescript/infra/config/environments/mainnet3/warp/configGetters/getBaseSolanamainnetTONYWarpConfig.ts +++ b/typescript/infra/config/environments/mainnet3/warp/configGetters/getBaseSolanamainnetTONYWarpConfig.ts @@ -34,7 +34,7 @@ export async function getBaseSolanamainnetTONYWarpConfig( mailbox: routerConfig.solanamainnet.mailbox, owner: solanamainnetOwner, type: TokenType.synthetic, - foreignDeployment: '4AQVPTCAeLswnjksQdutxUDuxEJxUBwoWmVimGuPtGSt', + foreignDeployment: 'Fa4zQJCH7id5KL1eFJt2mHyFpUNfCCSkHgtMrLvrRJBN', gas: SEALEVEL_WARP_ROUTE_HANDLER_GAS_AMOUNT, interchainSecurityModule: ethers.constants.AddressZero, }; diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumSuperseedWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumSuperseedWarpConfig.ts new file mode 100644 index 0000000000..e6ea73782d --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumSuperseedWarpConfig.ts @@ -0,0 +1,65 @@ +import { ethers } from 'ethers'; + +import { ChainMap, HypTokenRouterConfig, TokenType } from '@hyperlane-xyz/sdk'; + +import { + RouterConfigWithoutOwner, + tokens, +} from '../../../../../src/config/warp.js'; + +const owners = { + ethereum: '0xa7eccdb9be08178f896c26b7bbd8c3d4e844d9ba', + superseed: '0xa7eccdb9be08178f896c26b7bbd8c3d4e844d9ba', +}; + +const ISM_CONFIG = ethers.constants.AddressZero; // Default ISM + +export const getEthereumSuperseedCBBTCWarpConfig = async ( + routerConfig: ChainMap, +): Promise> => { + const ethereum: HypTokenRouterConfig = { + ...routerConfig.ethereum, + owner: owners.ethereum, + type: TokenType.collateral, + token: tokens.ethereum.cbBTC, + interchainSecurityModule: ISM_CONFIG, + }; + + const superseed: HypTokenRouterConfig = { + ...routerConfig.superseed, + owner: owners.superseed, + type: TokenType.collateralFiat, + token: '0x6f36dbd829de9b7e077db8a35b480d4329ceb331', + interchainSecurityModule: ISM_CONFIG, + }; + + return { + ethereum, + superseed, + }; +}; + +export const getEthereumSuperseedUSDCWarpConfig = async ( + routerConfig: ChainMap, +): Promise> => { + const ethereum: HypTokenRouterConfig = { + ...routerConfig.ethereum, + owner: owners.ethereum, + type: TokenType.collateral, + token: tokens.ethereum.USDC, + interchainSecurityModule: ISM_CONFIG, + }; + + const superseed: HypTokenRouterConfig = { + ...routerConfig.superseed, + owner: owners.superseed, + type: TokenType.collateralFiat, + token: '0xc316c8252b5f2176d0135ebb0999e99296998f2e', + interchainSecurityModule: ISM_CONFIG, + }; + + return { + ethereum, + superseed, + }; +}; diff --git a/typescript/infra/config/environments/mainnet3/warp/warpIds.ts b/typescript/infra/config/environments/mainnet3/warp/warpIds.ts index 3e8ea485e5..39bbf41de0 100644 --- a/typescript/infra/config/environments/mainnet3/warp/warpIds.ts +++ b/typescript/infra/config/environments/mainnet3/warp/warpIds.ts @@ -47,6 +47,8 @@ export enum WarpRouteIds { ArbitrumBaseBlastBscEthereumGnosisLiskMantleModeOptimismPolygonScrollZeroNetworkZoraMainnet = 'ETH/arbitrum-base-blast-bsc-ethereum-gnosis-lisk-mantle-mode-optimism-polygon-scroll-zeronetwork-zoramainnet', AppchainBaseUSDC = 'USDC/appchain-base', BobaBsquaredSwellUBTC = 'UBTC/boba-bsquared-swell', + EthereumSuperseedCBBTC = 'CBBTC/ethereum-superseed', + EthereumSuperseedUSDC = 'USDC/ethereum-superseed', EthereumFormUSDT = 'USDT/ethereum-form', EthereumFormUSDC = 'USDC/ethereum-form', EthereumSuperseedUSDT = 'USDT/ethereum-superseed', diff --git a/typescript/infra/config/warp.ts b/typescript/infra/config/warp.ts index 6374d05ae7..6a9e5499e7 100644 --- a/typescript/infra/config/warp.ts +++ b/typescript/infra/config/warp.ts @@ -50,6 +50,10 @@ import { getEthereumInevmUSDTWarpConfig } from './environments/mainnet3/warp/con import { getEthereumInkUSDCConfig } from './environments/mainnet3/warp/configGetters/getEthereumInkUSDCWarpConfig.js'; import { getEthereumSeiFastUSDWarpConfig } from './environments/mainnet3/warp/configGetters/getEthereumSeiFastUSDWarpConfig.js'; import { getEthereumSeiPumpBTCWarpConfig } from './environments/mainnet3/warp/configGetters/getEthereumSeiPumpBTCWarpConfig.js'; +import { + getEthereumSuperseedCBBTCWarpConfig, + getEthereumSuperseedUSDCWarpConfig, +} from './environments/mainnet3/warp/configGetters/getEthereumSuperseedWarpConfig.js'; import { getEthereumVictionETHWarpConfig } from './environments/mainnet3/warp/configGetters/getEthereumVictionETHWarpConfig.js'; import { getEthereumVictionUSDCWarpConfig } from './environments/mainnet3/warp/configGetters/getEthereumVictionUSDCWarpConfig.js'; import { getEthereumVictionUSDTWarpConfig } from './environments/mainnet3/warp/configGetters/getEthereumVictionUSDTWarpConfig.js'; @@ -115,6 +119,8 @@ export const warpConfigGetterMap: Record = { [WarpRouteIds.AppchainBaseUSDC]: getAppChainBaseUSDCWarpConfig, [WarpRouteIds.BobaBsquaredSwellUBTC]: getBobaBsquaredSwellUBTCWarpConfig, [WarpRouteIds.EthereumZircuitRe7LRT]: getEthereumZircuitRe7LRTWarpConfig, + [WarpRouteIds.EthereumSuperseedCBBTC]: getEthereumSuperseedCBBTCWarpConfig, + [WarpRouteIds.EthereumSuperseedUSDC]: getEthereumSuperseedUSDCWarpConfig, [WarpRouteIds.EthereumFormUSDT]: getEthereumFormUSDTWarpConfig, [WarpRouteIds.EthereumFormUSDC]: getEthereumFormUSDCWarpConfig, [WarpRouteIds.EthereumSuperseedUSDT]: getEthereumSuperseedUSDTConfig, diff --git a/typescript/infra/helm/warp-routes/templates/_helpers.tpl b/typescript/infra/helm/warp-routes/templates/_helpers.tpl index 2a9424b0a9..d96f267e05 100644 --- a/typescript/infra/helm/warp-routes/templates/_helpers.tpl +++ b/typescript/infra/helm/warp-routes/templates/_helpers.tpl @@ -68,12 +68,14 @@ The warp-routes container image: {{ .Values.image.repository }}:{{ .Values.image.tag }} imagePullPolicy: IfNotPresent env: - - name: LOG_FORMAT - value: json - command: + - name: LOG_FORMAT + value: json + - name: REGISTRY_COMMIT + value: {{ .Values.hyperlane.registryCommit }} + args: - ./node_modules/.bin/tsx - ./typescript/infra/scripts/warp-routes/monitor/monitor-warp-route-balances.ts - - -v + - -v - "30000" - --warpRouteId - {{ .Values.warpRouteId }} diff --git a/typescript/infra/helm/warp-routes/values.yaml b/typescript/infra/helm/warp-routes/values.yaml index a7a09d817a..69d2be36d5 100644 --- a/typescript/infra/helm/warp-routes/values.yaml +++ b/typescript/infra/helm/warp-routes/values.yaml @@ -5,6 +5,7 @@ hyperlane: runEnv: mainnet3 context: hyperlane chains: [] + registryCommit: nameOverride: '' fullnameOverride: '' externalSecrets: diff --git a/typescript/infra/package.json b/typescript/infra/package.json index 637c926bae..952bd4351c 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": "8.2.0", + "version": "8.3.0", "dependencies": { "@arbitrum/sdk": "^4.0.0", "@aws-sdk/client-iam": "^3.74.0", @@ -13,10 +13,10 @@ "@ethersproject/hardware-wallets": "^5.7.0", "@ethersproject/providers": "*", "@google-cloud/secret-manager": "^5.5.0", - "@hyperlane-xyz/helloworld": "8.2.0", + "@hyperlane-xyz/helloworld": "8.3.0", "@hyperlane-xyz/registry": "7.1.0", - "@hyperlane-xyz/sdk": "8.2.0", - "@hyperlane-xyz/utils": "8.2.0", + "@hyperlane-xyz/sdk": "8.3.0", + "@hyperlane-xyz/utils": "8.3.0", "@inquirer/prompts": "3.3.2", "@nomiclabs/hardhat-etherscan": "^3.0.3", "@safe-global/api-kit": "1.3.0", diff --git a/typescript/infra/scripts/agent-utils.ts b/typescript/infra/scripts/agent-utils.ts index 2b0a788b2b..260c51956b 100644 --- a/typescript/infra/scripts/agent-utils.ts +++ b/typescript/infra/scripts/agent-utils.ts @@ -286,15 +286,6 @@ export function withRpcUrls(args: Argv) { .alias('r', 'rpcUrls'); } -export function withTxHashes(args: Argv) { - return args - .describe('txHashes', 'transaction hash') - .string('txHashes') - .array('txHashes') - .demandOption('txHashes') - .alias('t', 'txHashes'); -} - // Interactively gets a single warp route ID export async function getWarpRouteIdInteractive() { const choices = Object.values(WarpRouteIds).map((id) => ({ diff --git a/typescript/infra/scripts/safes/get-pending-txs.ts b/typescript/infra/scripts/safes/get-pending-txs.ts index 13d8c15d88..ead1b69c74 100644 --- a/typescript/infra/scripts/safes/get-pending-txs.ts +++ b/typescript/infra/scripts/safes/get-pending-txs.ts @@ -1,118 +1,25 @@ import { confirm } from '@inquirer/prompts'; import chalk from 'chalk'; -import { formatUnits } from 'ethers/lib/utils.js'; import yargs from 'yargs'; -import { MultiProvider } from '@hyperlane-xyz/sdk'; -import { LogFormat, LogLevel, configureRootLogger } from '@hyperlane-xyz/utils'; +import { + LogFormat, + LogLevel, + configureRootLogger, + rootLogger, +} from '@hyperlane-xyz/utils'; import { Contexts } from '../../config/contexts.js'; import { safes } from '../../config/environments/mainnet3/owners.js'; import { Role } from '../../src/roles.js'; -import { executeTx, getSafeAndService } from '../../src/utils/safe.js'; +import { + SafeTxStatus, + executeTx, + getPendingTxsForChains, +} from '../../src/utils/safe.js'; import { withChains } from '../agent-utils.js'; import { getEnvironmentConfig } from '../core-utils.js'; -export enum SafeTxStatus { - NO_CONFIRMATIONS = '🔴', - PENDING = '🟡', - ONE_AWAY = '🔵', - READY_TO_EXECUTE = '🟢', -} - -type SafeStatus = { - chain: string; - nonce: number; - submissionDate: string; - shortTxHash: string; - fullTxHash: string; - confs: number; - threshold: number; - status: string; - balance: string; -}; - -export async function getPendingTxsForChains( - chains: string[], - multiProvider: MultiProvider, -): Promise { - const txs: SafeStatus[] = []; - await Promise.all( - chains.map(async (chain) => { - if (!safes[chain]) { - console.error(chalk.red.bold(`No safe found for ${chain}`)); - return; - } - - if (chain === 'endurance') { - console.info( - chalk.gray.italic( - `Skipping chain ${chain} as it does not have a functional safe API`, - ), - ); - return; - } - - let safeSdk, safeService; - try { - ({ safeSdk, safeService } = await getSafeAndService( - chain, - multiProvider, - safes[chain], - )); - } catch (error) { - console.warn( - chalk.yellow( - `Skipping chain ${chain} as there was an error getting the safe service: ${error}`, - ), - ); - return; - } - - const threshold = await safeSdk.getThreshold(); - const pendingTxs = await safeService.getPendingTransactions(safes[chain]); - if (pendingTxs.results.length === 0) { - return; - } - - const balance = await safeSdk.getBalance(); - const nativeToken = await multiProvider.getNativeToken(chain); - const formattedBalance = formatUnits(balance, nativeToken.decimals); - - pendingTxs.results.forEach( - ({ nonce, submissionDate, safeTxHash, confirmations }) => { - const confs = confirmations?.length ?? 0; - const status = - confs >= threshold - ? SafeTxStatus.READY_TO_EXECUTE - : confs === 0 - ? SafeTxStatus.NO_CONFIRMATIONS - : threshold - confs - ? SafeTxStatus.ONE_AWAY - : SafeTxStatus.PENDING; - - txs.push({ - chain, - nonce, - submissionDate: new Date(submissionDate).toDateString(), - shortTxHash: `${safeTxHash.slice(0, 6)}...${safeTxHash.slice(-4)}`, - fullTxHash: safeTxHash, - confs, - threshold, - status, - balance: `${Number(formattedBalance).toFixed(5)} ${ - nativeToken.symbol - }`, - }); - }, - ); - }), - ); - return txs.sort( - (a, b) => a.chain.localeCompare(b.chain) || a.nonce - b.nonce, - ); -} - async function main() { const safeChains = Object.keys(safes); configureRootLogger(LogFormat.Pretty, LogLevel.Info); @@ -129,7 +36,7 @@ async function main() { const chainsToCheck = chains || safeChains; if (chainsToCheck.length === 0) { - console.error('No chains provided'); + rootLogger.error('No chains provided'); process.exit(1); } @@ -141,11 +48,16 @@ async function main() { chainsToCheck, ); - const pendingTxs = await getPendingTxsForChains(chainsToCheck, multiProvider); + const pendingTxs = await getPendingTxsForChains( + chainsToCheck, + multiProvider, + safes, + ); if (pendingTxs.length === 0) { - console.info(chalk.green('No pending transactions found!')); + rootLogger.info(chalk.green('No pending transactions found!')); process.exit(0); } + // eslint-disable-next-line no-console console.table(pendingTxs, [ 'chain', 'nonce', @@ -161,7 +73,7 @@ async function main() { (tx) => tx.status === SafeTxStatus.READY_TO_EXECUTE, ); if (executableTxs.length === 0) { - console.info(chalk.green('No transactions to execute!')); + rootLogger.info(chalk.green('No transactions to execute!')); process.exit(0); } @@ -171,7 +83,7 @@ async function main() { }); if (!shouldExecute) { - console.info( + rootLogger.info( chalk.blue( `${executableTxs.length} transactions available for execution`, ), @@ -179,7 +91,7 @@ async function main() { process.exit(0); } - console.info(chalk.blueBright('Executing transactions...')); + rootLogger.info(chalk.blueBright('Executing transactions...')); for (const tx of executableTxs) { const confirmExecuteTx = await confirm({ @@ -187,7 +99,7 @@ async function main() { default: false, }); if (confirmExecuteTx) { - console.log( + rootLogger.info( `Executing transaction ${tx.shortTxHash} on chain ${tx.chain}`, ); try { @@ -198,7 +110,7 @@ async function main() { tx.fullTxHash, ); } catch (error) { - console.error(chalk.red(`Error executing transaction: ${error}`)); + rootLogger.error(chalk.red(`Error executing transaction: ${error}`)); return; } } @@ -210,6 +122,6 @@ async function main() { main() .then() .catch((e) => { - console.error(e); + rootLogger.error(e); process.exit(1); }); diff --git a/typescript/infra/scripts/safes/parse-txs.ts b/typescript/infra/scripts/safes/parse-txs.ts index 21e8f87eed..6df936817d 100644 --- a/typescript/infra/scripts/safes/parse-txs.ts +++ b/typescript/infra/scripts/safes/parse-txs.ts @@ -1,23 +1,29 @@ +import chalk from 'chalk'; import { BigNumber } from 'ethers'; +import yargs from 'yargs'; import { AnnotatedEV5Transaction } from '@hyperlane-xyz/sdk'; import { LogFormat, LogLevel, configureRootLogger, + rootLogger, stringifyObject, } from '@hyperlane-xyz/utils'; +import { safes } from '../../config/environments/mainnet3/owners.js'; import { GovernTransactionReader } from '../../src/tx/govern-transaction-reader.js'; -import { getSafeTx } from '../../src/utils/safe.js'; -import { getArgs, withChainsRequired, withTxHashes } from '../agent-utils.js'; +import { getPendingTxsForChains, getSafeTx } from '../../src/utils/safe.js'; +import { writeYamlAtPath } from '../../src/utils/utils.js'; +import { withChains } from '../agent-utils.js'; import { getEnvironmentConfig, getHyperlaneCore } from '../core-utils.js'; -async function main() { - const { environment, chains, txHashes } = await withTxHashes( - withChainsRequired(getArgs()), - ).argv; +const environment = 'mainnet3'; +const safeChains = Object.keys(safes); +async function main() { + const { chains } = await withChains(yargs(process.argv.slice(2)), safeChains) + .argv; configureRootLogger(LogFormat.Pretty, LogLevel.Info); const config = getEnvironmentConfig(environment); @@ -35,11 +41,31 @@ async function main() { warpRoutes, ); + const pendingTxs = await getPendingTxsForChains( + !chains || chains.length === 0 ? safeChains : chains, + multiProvider, + safes, + ); + if (pendingTxs.length === 0) { + rootLogger.info(chalk.green('No pending transactions found!')); + process.exit(0); + } + // eslint-disable-next-line no-console + console.table(pendingTxs, [ + 'chain', + 'nonce', + 'submissionDate', + 'fullTxHash', + 'confs', + 'threshold', + 'status', + 'balance', + ]); + const chainResultEntries = await Promise.all( - chains.map(async (chain, chainIndex) => { - const txHash = txHashes[chainIndex]; - console.log(`Reading tx ${txHash} on ${chain}`); - const safeTx = await getSafeTx(chain, multiProvider, txHash); + pendingTxs.map(async ({ chain, fullTxHash }) => { + rootLogger.info(`Reading tx ${fullTxHash} on ${chain}`); + const safeTx = await getSafeTx(chain, multiProvider, fullTxHash); const tx: AnnotatedEV5Transaction = { to: safeTx.to, data: safeTx.data, @@ -48,27 +74,30 @@ async function main() { try { const results = await reader.read(chain, tx); - console.log(`Finished reading tx ${txHash} on ${chain}`); - return [chain, results]; + rootLogger.info(`Finished reading tx ${fullTxHash} on ${chain}`); + return [`${chain}-${fullTxHash}`, results]; } catch (err) { - console.error('Error reading transaction', err, chain, tx); + rootLogger.error('Error reading transaction', err, chain, tx); process.exit(1); } }), ); - const chainResults = Object.fromEntries(chainResultEntries); - console.log(stringifyObject(chainResults, 'yaml', 2)); - if (reader.errors.length) { - console.error('❌❌❌❌❌ Encountered fatal errors ❌❌❌❌❌'); - console.log(stringifyObject(reader.errors, 'yaml', 2)); - console.error('❌❌❌❌❌ Encountered fatal errors ❌❌❌❌❌'); + rootLogger.error('❌❌❌❌❌ Encountered fatal errors ❌❌❌❌❌'); + rootLogger.info(stringifyObject(reader.errors, 'yaml', 2)); + rootLogger.error('❌❌❌❌❌ Encountered fatal errors ❌❌❌❌❌'); process.exit(1); + } else { + rootLogger.info('✅✅✅✅✅ No fatal errors ✅✅✅✅✅'); + const chainResults = Object.fromEntries(chainResultEntries); + const resultsPath = `safe-tx-results-${Date.now()}.yaml`; + writeYamlAtPath(resultsPath, chainResults); + rootLogger.info(`Results written to ${resultsPath}`); } } main().catch((err) => { - console.error('Error:', err); + rootLogger.error('Error:', err); process.exit(1); }); diff --git a/typescript/infra/scripts/warp-routes/deploy-warp-monitor.ts b/typescript/infra/scripts/warp-routes/deploy-warp-monitor.ts index 8ede7cdda5..d58af0eac6 100644 --- a/typescript/infra/scripts/warp-routes/deploy-warp-monitor.ts +++ b/typescript/infra/scripts/warp-routes/deploy-warp-monitor.ts @@ -1,7 +1,16 @@ -import { checkbox } from '@inquirer/prompts'; +import { input } from '@inquirer/prompts'; +import chalk from 'chalk'; +import { execSync } from 'child_process'; + +import { + LogFormat, + LogLevel, + configureRootLogger, + rootLogger, +} from '@hyperlane-xyz/utils'; import { Contexts } from '../../config/contexts.js'; -import { WarpRouteIds } from '../../config/environments/mainnet3/warp/warpIds.js'; +import { getRegistry } from '../../config/registry.js'; import { HelmCommand } from '../../src/utils/helm.js'; import { WarpRouteMonitorHelmManager } from '../../src/warp/helm.js'; import { @@ -13,7 +22,26 @@ import { } from '../agent-utils.js'; import { getEnvironmentConfig } from '../core-utils.js'; +async function validateRegistryCommit(commit: string) { + const registry = getRegistry(); + const registryUri = registry.getUri(); + + try { + rootLogger.info( + chalk.grey.italic(`Attempting to fetch registry commit ${commit}...`), + ); + execSync(`cd ${registryUri} && git fetch origin ${commit}`, { + stdio: 'inherit', + }); + rootLogger.info(chalk.grey.italic('Fetch completed successfully.')); + } catch (_) { + rootLogger.error(chalk.red(`Unable to fetch registry commit ${commit}.`)); + process.exit(1); + } +} + async function main() { + configureRootLogger(LogFormat.Pretty, LogLevel.Info); const { environment, warpRouteId } = await withWarpRouteId(getArgs()).argv; let warpRouteIds; @@ -23,6 +51,12 @@ async function main() { warpRouteIds = await getWarpRouteIdsInteractive(); } + const registryCommit = await input({ + message: + 'Enter the registry version to use (can be a commit, branch or tag):', + }); + await validateRegistryCommit(registryCommit); + await assertCorrectKubeContext(getEnvironmentConfig(environment)); const agentConfig = getAgentConfig(Contexts.Hyperlane, environment); @@ -31,6 +65,7 @@ async function main() { warpRouteId, environment, agentConfig.environmentChainNames, + registryCommit, ); await helmManager.runHelmCommand(HelmCommand.InstallOrUpgrade); }; @@ -42,11 +77,11 @@ async function main() { ); for (const id of warpRouteIds) { - console.log(`Deploying Warp Monitor for Warp Route ID: ${id}`); + rootLogger.info(`Deploying Warp Monitor for Warp Route ID: ${id}`); await deployWarpMonitor(id); } } main() - .then(() => console.log('Deploy successful!')) - .catch(console.error); + .then(() => rootLogger.info('Deploy successful!')) + .catch(rootLogger.error); diff --git a/typescript/infra/scripts/warp-routes/generate-warp-config.ts b/typescript/infra/scripts/warp-routes/generate-warp-config.ts index 47a39c16d8..c482f1e86b 100644 --- a/typescript/infra/scripts/warp-routes/generate-warp-config.ts +++ b/typescript/infra/scripts/warp-routes/generate-warp-config.ts @@ -1,6 +1,7 @@ import { stringify as yamlStringify } from 'yaml'; import { WarpRouteDeployConfigSchema } from '@hyperlane-xyz/sdk'; +import { rootLogger } from '@hyperlane-xyz/utils'; import { getWarpConfig } from '../../config/warp.js'; import { writeYamlAtPath } from '../../src/utils/utils.js'; @@ -23,17 +24,19 @@ async function main() { const parsed = WarpRouteDeployConfigSchema.safeParse(warpConfig); if (!parsed.success) { + rootLogger.error('Error parsing warp config:'); + console.dir(warpConfig, { depth: null }); console.dir(parsed.error.format(), { depth: null }); return; } - console.log('Warp config:'); - console.log(yamlStringify(parsed.data, null, 2)); + rootLogger.info('Warp config:'); + rootLogger.info(yamlStringify(parsed.data, null, 2)); if (outFile) { - console.log(`Writing config to ${outFile}`); + rootLogger.info(`Writing config to ${outFile}`); writeYamlAtPath(outFile, parsed.data); } } -main().catch((err) => console.error('Error:', err)); +main().catch((err) => rootLogger.error('Error:', err)); diff --git a/typescript/infra/scripts/warp-routes/monitor/monitor-warp-route-balances.ts b/typescript/infra/scripts/warp-routes/monitor/monitor-warp-route-balances.ts index c04ed7418c..f2c971b2ed 100644 --- a/typescript/infra/scripts/warp-routes/monitor/monitor-warp-route-balances.ts +++ b/typescript/infra/scripts/warp-routes/monitor/monitor-warp-route-balances.ts @@ -9,7 +9,6 @@ import { EvmHypXERC20LockboxAdapter, IHypXERC20Adapter, MultiProtocolProvider, - RouterConfig, SealevelHypTokenAdapter, Token, TokenStandard, @@ -18,10 +17,7 @@ import { import { ProtocolType, objMap, objMerge, sleep } from '@hyperlane-xyz/utils'; import { getWarpCoreConfig } from '../../../config/registry.js'; -import { - DeployEnvironment, - getRouterConfigsForAllVms, -} from '../../../src/config/environment.js'; +import { DeployEnvironment } from '../../../src/config/environment.js'; import { fetchGCPSecret } from '../../../src/utils/gcloud.js'; import { startMetricsServer } from '../../../src/utils/metrics.js'; import { @@ -59,16 +55,12 @@ async function main() { const envConfig = getEnvironmentConfig(environment); const registry = await envConfig.getRegistry(); const chainMetadata = await registry.getMetadata(); + const chainAddresses = await registry.getAddresses(); // The Sealevel warp adapters require the Mailbox address, so we - // get router configs (that include the Mailbox address) for all chains - // and merge them with the chain metadata. - const routerConfig = await getRouterConfigsForAllVms( - envConfig, - await envConfig.getMultiProvider(), - ); - const mailboxes = objMap(routerConfig, (_chain, config: RouterConfig) => ({ - mailbox: config.mailbox, + // get mailboxes for all chains and merge them with the chain metadata. + const mailboxes = objMap(chainAddresses, (_, { mailbox }) => ({ + mailbox, })); const multiProtocolProvider = new MultiProtocolProvider( objMerge(chainMetadata, mailboxes), diff --git a/typescript/infra/scripts/warp-routes/utils.ts b/typescript/infra/scripts/warp-routes/utils.ts index 2e0fdc40b9..1c843c4fc5 100644 --- a/typescript/infra/scripts/warp-routes/utils.ts +++ b/typescript/infra/scripts/warp-routes/utils.ts @@ -19,7 +19,7 @@ export async function getRouterConfig() { ).argv; const envConfig = getEnvironmentConfig(environment); - let multiProvider = await envConfig.getMultiProvider( + const multiProvider = await envConfig.getMultiProvider( context, Role.Deployer, true, diff --git a/typescript/infra/src/agents/index.ts b/typescript/infra/src/agents/index.ts index 24fccbac07..cd1b1b206d 100644 --- a/typescript/infra/src/agents/index.ts +++ b/typescript/infra/src/agents/index.ts @@ -1,4 +1,3 @@ -import fs from 'fs'; import { join } from 'path'; import { @@ -49,11 +48,6 @@ const HELM_CHART_PATH = join( '/../../rust/main/helm/hyperlane-agent/', ); -if (!fs.existsSync(HELM_CHART_PATH + 'Chart.yaml')) - console.warn( - `Could not find helm chart at ${HELM_CHART_PATH}; the relative path may have changed.`, - ); - export abstract class AgentHelmManager extends HelmManager { abstract readonly role: AgentRole; readonly helmChartPath: string = HELM_CHART_PATH; diff --git a/typescript/infra/src/config/warp.ts b/typescript/infra/src/config/warp.ts index 0954bb71af..c91c7e13d1 100644 --- a/typescript/infra/src/config/warp.ts +++ b/typescript/infra/src/config/warp.ts @@ -1,8 +1,7 @@ -import { ChainMap, OwnableConfig, RouterConfig } from '@hyperlane-xyz/sdk'; -import { Address } from '@hyperlane-xyz/utils'; +import { OwnableConfig, RouterConfig } from '@hyperlane-xyz/sdk'; // Common collateral tokens to be used by warp route deployments. -export const tokens: ChainMap> = { +export const tokens = { ethereum: { amphrETH: '0x5fD13359Ba15A84B76f7F87568309040176167cd', apxETH: '0x9ba021b0a9b958b5e75ce9f6dff97c7ee52cb3e6', diff --git a/typescript/infra/src/tx/govern-transaction-reader.ts b/typescript/infra/src/tx/govern-transaction-reader.ts index ef82ab1e1a..7e21335a17 100644 --- a/typescript/infra/src/tx/govern-transaction-reader.ts +++ b/typescript/infra/src/tx/govern-transaction-reader.ts @@ -8,7 +8,12 @@ import assert from 'assert'; import chalk from 'chalk'; import { BigNumber, ethers } from 'ethers'; -import { ProxyAdmin__factory, TokenRouter__factory } from '@hyperlane-xyz/core'; +import { + Ownable__factory, + ProxyAdmin__factory, + TimelockController__factory, + TokenRouter__factory, +} from '@hyperlane-xyz/core'; import { AnnotatedEV5Transaction, ChainMap, @@ -35,6 +40,7 @@ import { icaOwnerChain, icas, safes, + timelocks, } from '../../config/environments/mainnet3/owners.js'; import { DeployEnvironment } from '../config/environment.js'; import { getSafeAndService } from '../utils/safe.js'; @@ -128,6 +134,11 @@ export class GovernTransactionReader { return this.readProxyAdminTransaction(chain, tx); } + // If it's to a TimelockController + if (this.isTimelockControllerTransaction(chain, tx)) { + return this.readTimelockControllerTransaction(chain, tx); + } + // If it's a Multisend if (await this.isMultisendTransaction(chain, tx)) { return this.readMultisendTransaction(chain, tx); @@ -138,6 +149,11 @@ export class GovernTransactionReader { return this.readWarpModuleTransaction(chain, tx); } + // If it's an Ownable transaction + if (await this.isOwnableTransaction(chain, tx)) { + return this.readOwnableTransaction(chain, tx); + } + const insight = '⚠️ Unknown transaction type'; // If we get here, it's an unknown transaction this.errors.push({ @@ -153,6 +169,73 @@ export class GovernTransactionReader { }; } + private isTimelockControllerTransaction( + chain: ChainName, + tx: AnnotatedEV5Transaction, + ): boolean { + return ( + tx.to !== undefined && + timelocks[chain] !== undefined && + eqAddress(tx.to!, timelocks[chain]!) + ); + } + + private async readTimelockControllerTransaction( + chain: ChainName, + tx: AnnotatedEV5Transaction, + ): Promise { + if (!tx.data) { + throw new Error('No data in TimelockController transaction'); + } + + const timelockControllerInterface = + TimelockController__factory.createInterface(); + const decoded = timelockControllerInterface.parseTransaction({ + data: tx.data, + value: tx.value, + }); + + let insight; + if ( + decoded.functionFragment.name === + timelockControllerInterface.functions[ + 'schedule(address,uint256,bytes,bytes32,bytes32,uint256)' + ].name + ) { + const [target, value, data, eta, executor, delay] = decoded.args; + insight = `Schedule ${target} to be executed at ${eta} with ${value} ${data}. Executor: ${executor}, Delay: ${delay}`; + } + + if ( + decoded.functionFragment.name === + timelockControllerInterface.functions[ + 'execute(address,uint256,bytes,bytes32,bytes32)' + ].name + ) { + const [target, value, data, executor] = decoded.args; + insight = `Execute ${target} with ${value} ${data}. Executor: ${executor}`; + } + + if ( + decoded.functionFragment.name === + timelockControllerInterface.functions['cancel(bytes32)'].name + ) { + const [id] = decoded.args; + insight = `Cancel scheduled transaction ${id}`; + } + + const args = formatFunctionFragmentArgs( + decoded.args, + decoded.functionFragment, + ); + + return { + chain, + to: `Timelock Controller (${chain} ${tx.to})`, + ...(insight ? { insight } : { args }), + }; + } + private isWarpModuleTransaction( chain: ChainName, tx: AnnotatedEV5Transaction, @@ -180,13 +263,7 @@ export class GovernTransactionReader { value: tx.value, }); - const args = formatFunctionFragmentArgs( - decoded.args, - decoded.functionFragment, - ); - - let insight = ''; - + let insight; if ( decoded.functionFragment.name === tokenRouterInterface.functions['setHook(address)'].name @@ -255,16 +332,22 @@ export class GovernTransactionReader { insight = `Unenroll remote routers for ${insights.join(', ')}`; } + let ownableTx = {}; + if (!insight) { + ownableTx = await this.readOwnableTransaction(chain, tx); + } + assert(tx.to, 'Warp Module transaction must have a to address'); - const token = this.warpRouteIndex[chain][tx.to.toLowerCase()]; + const tokenAddress = tx.to.toLowerCase(); + const token = this.warpRouteIndex[chain][tokenAddress]; return { + ...ownableTx, chain, - to: `${token.symbol} (${token.name}, ${token.standard})`, + to: `${token.symbol} (${token.name}, ${token.standard}, ${tokenAddress})`, insight, value: `${ethers.utils.formatEther(decoded.value)} ${symbol}`, signature: decoded.signature, - args, }; } @@ -401,26 +484,11 @@ export class GovernTransactionReader { value: tx.value, }); - const args = formatFunctionFragmentArgs( - decoded.args, - decoded.functionFragment, - ); - - let insight; - if ( - decoded.functionFragment.name === - proxyAdminInterface.functions['transferOwnership(address)'].name - ) { - const [newOwner] = decoded.args; - insight = `Transfer ownership to ${newOwner}`; - } - + const ownableTx = await this.readOwnableTransaction(chain, tx); return { - chain, + ...ownableTx, to: `Proxy Admin (${chain} ${this.chainAddresses[chain].proxyAdmin})`, - insight, signature: decoded.signature, - args, }; } @@ -629,6 +697,49 @@ export class GovernTransactionReader { }; } + private async readOwnableTransaction( + chain: ChainName, + tx: AnnotatedEV5Transaction, + ): Promise { + if (!tx.data) { + throw new Error('⚠️ No data in Ownable transaction'); + } + + const ownableInterface = Ownable__factory.createInterface(); + const decoded = ownableInterface.parseTransaction({ + data: tx.data, + value: tx.value, + }); + + let insight; + if ( + decoded.functionFragment.name === + ownableInterface.functions['renounceOwnership()'].name + ) { + insight = `Renounce ownership`; + } + + if ( + decoded.functionFragment.name === + ownableInterface.functions['transferOwnership(address)'].name + ) { + const [newOwner] = decoded.args; + insight = `Transfer ownership to ${newOwner}`; + } + + const args = formatFunctionFragmentArgs( + decoded.args, + decoded.functionFragment, + ); + + return { + chain, + to: `Ownable (${chain} ${tx.to})`, + ...(insight ? { insight } : { args }), + signature: decoded.signature, + }; + } + isIcaTransaction(chain: ChainName, tx: AnnotatedEV5Transaction): boolean { return ( tx.to !== undefined && @@ -670,6 +781,23 @@ export class GovernTransactionReader { return eqAddress(multiSendCallOnlyAddress, tx.to); } + async isOwnableTransaction( + chain: ChainName, + tx: AnnotatedEV5Transaction, + ): Promise { + if (!tx.to) return false; + try { + const account = Ownable__factory.connect( + tx.to, + this.multiProvider.getProvider(chain), + ); + await account.owner(); + return true; + } catch { + return false; + } + } + private multiSendCallOnlyAddressCache: ChainMap = {}; async getMultiSendCallOnlyAddress( diff --git a/typescript/infra/src/utils/safe.ts b/typescript/infra/src/utils/safe.ts index 95e27c50e3..c85a4f6043 100644 --- a/typescript/infra/src/utils/safe.ts +++ b/typescript/infra/src/utils/safe.ts @@ -7,6 +7,7 @@ import { } from '@safe-global/safe-core-sdk-types'; import chalk from 'chalk'; import { BigNumber, ethers } from 'ethers'; +import { formatUnits } from 'ethers/lib/utils.js'; import { ChainNameOrId, @@ -14,9 +15,16 @@ import { getSafe, getSafeService, } from '@hyperlane-xyz/sdk'; -import { Address, CallData, eqAddress, retryAsync } from '@hyperlane-xyz/utils'; +import { + Address, + CallData, + eqAddress, + retryAsync, + rootLogger, +} from '@hyperlane-xyz/utils'; import safeSigners from '../../config/environments/mainnet3/safe/safeSigners.json' assert { type: 'json' }; +// eslint-disable-next-line import/no-cycle import { AnnotatedCallData } from '../govern/HyperlaneAppGovernor.js'; export async function getSafeAndService( @@ -82,7 +90,7 @@ export async function executeTx( await safeSdk.executeTransaction(safeTransaction); - console.log( + rootLogger.info( chalk.green.bold(`Executed transaction ${safeTxHash} on ${chain}`), ); } @@ -122,7 +130,7 @@ export async function proposeSafeTransaction( senderSignature: senderSignature.data, }); - console.log( + rootLogger.info( chalk.green(`Proposed transaction on ${chain} with hash ${safeTxHash}`), ); } @@ -143,7 +151,7 @@ export async function deleteAllPendingSafeTxs( }); if (!pendingTxsResponse.ok) { - console.error( + rootLogger.error( chalk.red(`Failed to fetch pending transactions for ${safeAddress}`), ); return; @@ -156,7 +164,7 @@ export async function deleteAllPendingSafeTxs( await deleteSafeTx(chain, multiProvider, safeAddress, tx.safeTxHash); } - console.log( + rootLogger.info( `Deleted all pending transactions on ${chain} for ${safeAddress}\n`, ); } @@ -177,7 +185,7 @@ export async function getSafeTx( }); if (!txDetailsResponse.ok) { - console.error( + rootLogger.error( chalk.red(`Failed to fetch transaction details for ${safeTxHash}`), ); return; @@ -205,7 +213,7 @@ export async function deleteSafeTx( }); if (!txDetailsResponse.ok) { - console.error( + rootLogger.error( chalk.red(`Failed to fetch transaction details for ${safeTxHash}`), ); return; @@ -215,21 +223,23 @@ export async function deleteSafeTx( const proposer = txDetails.proposer; if (!proposer) { - console.error(chalk.red(`No proposer found for transaction ${safeTxHash}`)); + rootLogger.error( + chalk.red(`No proposer found for transaction ${safeTxHash}`), + ); return; } // Compare proposer to signer const signerAddress = await signer.getAddress(); - if (proposer !== signerAddress) { - console.log( + if (!eqAddress(proposer, signerAddress)) { + rootLogger.info( chalk.italic( `Skipping deletion of transaction ${safeTxHash} proposed by ${proposer}`, ), ); return; } - console.log(`Deleting transaction ${safeTxHash} proposed by ${proposer}`); + rootLogger.info(`Deleting transaction ${safeTxHash} proposed by ${proposer}`); try { // Generate the EIP-712 signature @@ -275,7 +285,7 @@ export async function deleteSafeTx( }); if (res.status === 204) { - console.log( + rootLogger.info( chalk.green( `Successfully deleted transaction ${safeTxHash} on ${chain}`, ), @@ -284,13 +294,13 @@ export async function deleteSafeTx( } const errorBody = await res.text(); - console.error( + rootLogger.error( chalk.red( `Failed to delete transaction ${safeTxHash} on ${chain}: Status ${res.status} ${res.statusText}. Response body: ${errorBody}`, ), ); } catch (error) { - console.error( + rootLogger.error( chalk.red(`Failed to delete transaction ${safeTxHash} on ${chain}:`), error, ); @@ -310,8 +320,8 @@ export async function updateSafeOwner( (newOwner) => !owners.some((owner) => eqAddress(newOwner, owner)), ); - console.log(chalk.magentaBright('Owners to remove:', ownersToRemove)); - console.log(chalk.magentaBright('Owners to add:', ownersToAdd)); + rootLogger.info(chalk.magentaBright('Owners to remove:', ownersToRemove)); + rootLogger.info(chalk.magentaBright('Owners to add:', ownersToAdd)); const transactions: AnnotatedCallData[] = []; @@ -343,3 +353,104 @@ export async function updateSafeOwner( return transactions; } + +type SafeStatus = { + chain: string; + nonce: number; + submissionDate: string; + shortTxHash: string; + fullTxHash: string; + confs: number; + threshold: number; + status: string; + balance: string; +}; + +export enum SafeTxStatus { + NO_CONFIRMATIONS = '🔴', + PENDING = '🟡', + ONE_AWAY = '🔵', + READY_TO_EXECUTE = '🟢', +} + +export async function getPendingTxsForChains( + chains: string[], + multiProvider: MultiProvider, + safes: Record, +): Promise { + const txs: SafeStatus[] = []; + await Promise.all( + chains.map(async (chain) => { + if (!safes[chain]) { + rootLogger.error(chalk.red.bold(`No safe found for ${chain}`)); + return; + } + + if (chain === 'endurance') { + rootLogger.info( + chalk.gray.italic( + `Skipping chain ${chain} as it does not have a functional safe API`, + ), + ); + return; + } + + let safeSdk, safeService; + try { + ({ safeSdk, safeService } = await getSafeAndService( + chain, + multiProvider, + safes[chain], + )); + } catch (error) { + rootLogger.warn( + chalk.yellow( + `Skipping chain ${chain} as there was an error getting the safe service: ${error}`, + ), + ); + return; + } + + const threshold = await safeSdk.getThreshold(); + const pendingTxs = await safeService.getPendingTransactions(safes[chain]); + if (pendingTxs.results.length === 0) { + return; + } + + const balance = await safeSdk.getBalance(); + const nativeToken = await multiProvider.getNativeToken(chain); + const formattedBalance = formatUnits(balance, nativeToken.decimals); + + pendingTxs.results.forEach( + ({ nonce, submissionDate, safeTxHash, confirmations }) => { + const confs = confirmations?.length ?? 0; + const status = + confs >= threshold + ? SafeTxStatus.READY_TO_EXECUTE + : confs === 0 + ? SafeTxStatus.NO_CONFIRMATIONS + : threshold - confs + ? SafeTxStatus.ONE_AWAY + : SafeTxStatus.PENDING; + + txs.push({ + chain, + nonce, + submissionDate: new Date(submissionDate).toDateString(), + shortTxHash: `${safeTxHash.slice(0, 6)}...${safeTxHash.slice(-4)}`, + fullTxHash: safeTxHash, + confs, + threshold, + status, + balance: `${Number(formattedBalance).toFixed(5)} ${ + nativeToken.symbol + }`, + }); + }, + ); + }), + ); + return txs.sort( + (a, b) => a.chain.localeCompare(b.chain) || a.nonce - b.nonce, + ); +} diff --git a/typescript/infra/src/warp/helm.ts b/typescript/infra/src/warp/helm.ts index baff961634..1ccfa8aa3e 100644 --- a/typescript/infra/src/warp/helm.ts +++ b/typescript/infra/src/warp/helm.ts @@ -1,7 +1,7 @@ import { confirm } from '@inquirer/prompts'; import path from 'path'; -import { difference } from '@hyperlane-xyz/utils'; +import { difference, rootLogger } from '@hyperlane-xyz/utils'; import { WarpRouteIds } from '../../config/environments/mainnet3/warp/warpIds.js'; import { DeployEnvironment } from '../../src/config/environment.js'; @@ -20,6 +20,7 @@ export class WarpRouteMonitorHelmManager extends HelmManager { readonly warpRouteId: string, readonly runEnv: DeployEnvironment, readonly environmentChainNames: string[], + readonly registryCommit: string, ) { super(); } @@ -28,13 +29,14 @@ export class WarpRouteMonitorHelmManager extends HelmManager { return { image: { repository: 'gcr.io/abacus-labs-dev/hyperlane-monorepo', - tag: 'fe71e9b-20250121-110722', + tag: '49992bf-20250122-142014', }, warpRouteId: this.warpRouteId, fullnameOverride: this.helmReleaseName, environment: this.runEnv, hyperlane: { chains: this.environmentChainNames, + registryCommit: this.registryCommit, }, }; } @@ -90,7 +92,7 @@ export class WarpRouteMonitorHelmManager extends HelmManager { new Set(allExpectedHelmReleaseNames), ); for (const helmRelease of unknownHelmReleases) { - console.log( + rootLogger.warn( `Unknown Warp Monitor Helm Release: ${helmRelease} (possibly a release from a stale Warp Route ID).`, ); const uninstall = await confirm({ @@ -98,10 +100,10 @@ export class WarpRouteMonitorHelmManager extends HelmManager { "Would you like to uninstall this Helm Release? Make extra sure it shouldn't exist!", }); if (uninstall) { - console.log(`Uninstalling Helm Release: ${helmRelease}`); + rootLogger.info(`Uninstalling Helm Release: ${helmRelease}`); await removeHelmRelease(helmRelease, namespace); } else { - console.log(`Skipping uninstall of Helm Release: ${helmRelease}`); + rootLogger.info(`Skipping uninstall of Helm Release: ${helmRelease}`); } } } diff --git a/typescript/sdk/CHANGELOG.md b/typescript/sdk/CHANGELOG.md index 70e744bc74..af432bdb76 100644 --- a/typescript/sdk/CHANGELOG.md +++ b/typescript/sdk/CHANGELOG.md @@ -1,5 +1,19 @@ # @hyperlane-xyz/sdk +## 8.3.0 + +### Minor Changes + +- 7546c0181: Deploy to trumpchain. +- 49856fbb9: Deploy to flametestnet, sonicblaze. Remove support for sonictestnet. + +### Patch Changes + +- Updated dependencies [db8c09011] +- Updated dependencies [11cf66c5e] + - @hyperlane-xyz/core@5.10.0 + - @hyperlane-xyz/utils@8.3.0 + ## 8.2.0 ### Minor Changes diff --git a/typescript/sdk/package.json b/typescript/sdk/package.json index 9c0b130684..3a3c2342fc 100644 --- a/typescript/sdk/package.json +++ b/typescript/sdk/package.json @@ -1,18 +1,18 @@ { "name": "@hyperlane-xyz/sdk", "description": "The official SDK for the Hyperlane Network", - "version": "8.2.0", + "version": "8.3.0", "dependencies": { "@arbitrum/sdk": "^4.0.0", "@aws-sdk/client-s3": "^3.577.0", "@chain-registry/types": "^0.50.14", "@cosmjs/cosmwasm-stargate": "^0.32.4", "@cosmjs/stargate": "^0.32.4", - "@hyperlane-xyz/core": "5.9.2", - "@hyperlane-xyz/utils": "8.2.0", + "@hyperlane-xyz/core": "5.10.0", + "@hyperlane-xyz/utils": "8.3.0", "@safe-global/api-kit": "1.3.0", "@safe-global/protocol-kit": "1.3.0", - "@safe-global/safe-deployments": "1.37.8", + "@safe-global/safe-deployments": "1.37.23", "@solana/spl-token": "^0.4.9", "@solana/web3.js": "^1.95.4", "bignumber.js": "^9.1.1", diff --git a/typescript/sdk/src/token/types.ts b/typescript/sdk/src/token/types.ts index 400a40d263..9b76ea8afb 100644 --- a/typescript/sdk/src/token/types.ts +++ b/typescript/sdk/src/token/types.ts @@ -118,9 +118,21 @@ export const WarpRouteDeployConfigSchema = z const collateralRebaseEntry = Object.entries(warpRouteDeployConfig).find( ([_, config]) => isCollateralRebaseTokenConfig(config), ); - if (!collateralRebaseEntry) return warpRouteDeployConfig; // Pass through for other token types - if (isCollateralRebasePairedCorrectly(warpRouteDeployConfig)) { + const syntheticRebaseEntry = Object.entries(warpRouteDeployConfig).find( + ([_, config]) => isSyntheticRebaseTokenConfig(config), + ); + + // Require both collateral rebase and synthetic rebase to be present in the config + if (!collateralRebaseEntry && !syntheticRebaseEntry) { + // Pass through for other token types + return warpRouteDeployConfig; + } + + if ( + collateralRebaseEntry && + isCollateralRebasePairedCorrectly(warpRouteDeployConfig) + ) { const collateralChainName = collateralRebaseEntry[0]; return objMap(warpRouteDeployConfig, (_, config) => { if (config.type === TokenType.syntheticRebase) diff --git a/typescript/sdk/src/utils/gnosisSafe.js b/typescript/sdk/src/utils/gnosisSafe.js index 51235e2121..90f360821a 100644 --- a/typescript/sdk/src/utils/gnosisSafe.js +++ b/typescript/sdk/src/utils/gnosisSafe.js @@ -43,6 +43,16 @@ const safeDeploymentsVersions = { }, }; +// Override for chains that haven't yet been published in the safe-deployments package. +// Temporary until PR to safe-deployments package is merged and SDK dependency is updated. +const chainOverrides = { + // zeronetwork + 543210: { + multiSend: '0x0dFcccB95225ffB03c6FBB2559B530C2B7C8A912', + multiSendCallOnly: '0xf220D3b4DFb23C4ade8C88E526C1353AbAcbC38F', + }, +}; + export async function getSafe(chain, multiProvider, safeAddress) { // Create Ethers Adapter const signer = multiProvider.getSigner(chain); @@ -61,7 +71,16 @@ export async function getSafe(chain, multiProvider, safeAddress) { // Get the multiSend and multiSendCallOnly deployments for the given chain let multiSend, multiSendCallOnly; - if (safeDeploymentsVersions[safeVersion]) { + if (chainOverrides[chainId]) { + multiSend = { + networkAddresses: { [chainId]: chainOverrides[chainId].multiSend }, + }; + multiSendCallOnly = { + networkAddresses: { + [chainId]: chainOverrides[chainId].multiSendCallOnly, + }, + }; + } else if (safeDeploymentsVersions[safeVersion]) { const { multiSendVersion, multiSendCallOnlyVersion } = safeDeploymentsVersions[safeVersion]; multiSend = getMultiSendDeployment({ diff --git a/typescript/utils/CHANGELOG.md b/typescript/utils/CHANGELOG.md index ecddf9e23e..ed5d194e9d 100644 --- a/typescript/utils/CHANGELOG.md +++ b/typescript/utils/CHANGELOG.md @@ -1,5 +1,7 @@ # @hyperlane-xyz/utils +## 8.3.0 + ## 8.2.0 ## 8.1.0 diff --git a/typescript/utils/package.json b/typescript/utils/package.json index be8c55f665..7cf85eedae 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": "8.2.0", + "version": "8.3.0", "dependencies": { "@cosmjs/encoding": "^0.32.4", "@solana/web3.js": "^1.95.4", diff --git a/typescript/widgets/CHANGELOG.md b/typescript/widgets/CHANGELOG.md index 86f2f30c81..35cb068377 100644 --- a/typescript/widgets/CHANGELOG.md +++ b/typescript/widgets/CHANGELOG.md @@ -1,5 +1,18 @@ # @hyperlane-xyz/widgets +## 8.3.0 + +### Minor Changes + +- 25df8a35f: Refresh Icon and Tooltip class Name + +### Patch Changes + +- Updated dependencies [7546c0181] +- Updated dependencies [49856fbb9] + - @hyperlane-xyz/sdk@8.3.0 + - @hyperlane-xyz/utils@8.3.0 + ## 8.2.0 ### Patch Changes diff --git a/typescript/widgets/package.json b/typescript/widgets/package.json index c1b9548454..46bfc8bbc3 100644 --- a/typescript/widgets/package.json +++ b/typescript/widgets/package.json @@ -1,7 +1,7 @@ { "name": "@hyperlane-xyz/widgets", "description": "Common react components for Hyperlane projects", - "version": "8.2.0", + "version": "8.3.0", "peerDependencies": { "react": "^18", "react-dom": "^18" @@ -9,8 +9,8 @@ "dependencies": { "@cosmos-kit/react": "^2.18.0", "@headlessui/react": "^2.1.8", - "@hyperlane-xyz/sdk": "8.2.0", - "@hyperlane-xyz/utils": "8.2.0", + "@hyperlane-xyz/sdk": "8.3.0", + "@hyperlane-xyz/utils": "8.3.0", "@interchain-ui/react": "^1.23.28", "@rainbow-me/rainbowkit": "^2.2.0", "@solana/wallet-adapter-react": "^0.15.32", diff --git a/typescript/widgets/src/components/Tooltip.tsx b/typescript/widgets/src/components/Tooltip.tsx index e62c46816a..3b69bd18d4 100644 --- a/typescript/widgets/src/components/Tooltip.tsx +++ b/typescript/widgets/src/components/Tooltip.tsx @@ -1,3 +1,4 @@ +import { clsx } from 'clsx'; import React, { AnchorHTMLAttributes } from 'react'; import { PlacesType, Tooltip as ReactTooltip } from 'react-tooltip'; @@ -9,14 +10,16 @@ type Props = AnchorHTMLAttributes & { content: string; size?: number; placement?: PlacesType; + tooltipClassName?: string; }; export function Tooltip({ id, content, - size = 16, className, placement = 'top-start', + size = 16, + tooltipClassName, ...rest }: Props) { return ( @@ -26,6 +29,10 @@ export function Tooltip({ data-tooltip-place={placement} data-tooltip-id={id} data-tooltip-html={content} + data-tooltip-class-name={clsx( + 'htw-max-w-[calc(100%-10px)] sm:htw-max-w-[526px]', + tooltipClassName, + )} {...rest} > diff --git a/typescript/widgets/src/icons/Refresh.tsx b/typescript/widgets/src/icons/Refresh.tsx new file mode 100644 index 0000000000..2b9bbce03e --- /dev/null +++ b/typescript/widgets/src/icons/Refresh.tsx @@ -0,0 +1,18 @@ +import React, { memo } from 'react'; + +import { ColorPalette } from '../color.js'; + +import { DefaultIconProps } from './types.js'; + +function _RefreshIcon({ color, ...rest }: DefaultIconProps) { + return ( + + + + ); +} + +export const RefreshIcon = memo(_RefreshIcon); diff --git a/typescript/widgets/src/index.ts b/typescript/widgets/src/index.ts index 655e3c1731..83cae3ace0 100644 --- a/typescript/widgets/src/index.ts +++ b/typescript/widgets/src/index.ts @@ -45,6 +45,7 @@ export { PencilIcon } from './icons/Pencil.js'; export { PlusIcon } from './icons/Plus.js'; export { PlusCircleIcon } from './icons/PlusCircle.js'; export { QuestionMarkIcon } from './icons/QuestionMark.js'; +export { RefreshIcon } from './icons/Refresh.js'; export { SearchIcon } from './icons/Search.js'; export { ShieldIcon } from './icons/Shield.js'; export { SpinnerIcon } from './icons/Spinner.js'; diff --git a/typescript/widgets/src/stories/Tooltip.stories.tsx b/typescript/widgets/src/stories/Tooltip.stories.tsx new file mode 100644 index 0000000000..b758eb7409 --- /dev/null +++ b/typescript/widgets/src/stories/Tooltip.stories.tsx @@ -0,0 +1,27 @@ +import { Meta, StoryObj } from '@storybook/react'; + +import { Tooltip } from '../components/Tooltip.js'; + +const meta = { + title: 'Tooltip', + component: Tooltip, +} satisfies Meta; +export default meta; +type Story = StoryObj; + +export const DefaultTooltip = { + args: { + id: 'id-01', + content: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut mattis odio ac sem dictum tincidunt. Suspendisse interdum purus et quam ornare, at tempor risus pretium.', + }, +} satisfies Story; + +export const WithTooltipClassnameTooltip = { + args: { + id: 'id-01', + content: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut mattis odio ac sem dictum tincidunt. Suspendisse interdum purus et quam ornare, at tempor risus pretium.', + tooltipClassName: 'sm:htw-max-w-[300px]', + }, +} satisfies Story; diff --git a/yarn.lock b/yarn.lock index 54fa622784..7cb1ed49c4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4240,6 +4240,13 @@ __metadata: languageName: node linkType: hard +"@balena/dockerignore@npm:^1.0.2": + version: 1.0.2 + resolution: "@balena/dockerignore@npm:1.0.2" + checksum: 10/13d654fdd725008577d32e721c720275bdc48f72bce612326363d5bed449febbed856c517a0b23c7c40d87cb531e63432804550b4ecc13e365d26fee38fb6c8a + languageName: node + linkType: hard + "@base2/pretty-print-object@npm:1.0.1": version: 1.0.1 resolution: "@base2/pretty-print-object@npm:1.0.1" @@ -7214,6 +7221,16 @@ __metadata: languageName: node linkType: hard +"@grpc/grpc-js@npm:^1.11.1": + version: 1.12.5 + resolution: "@grpc/grpc-js@npm:1.12.5" + dependencies: + "@grpc/proto-loader": "npm:^0.7.13" + "@js-sdsl/ordered-map": "npm:^4.4.2" + checksum: 10/4f8ead236dcab4d94e15e62d65ad2d93732d37f5cc52ffafe67ae00f69eae4a4c97d6d34a1b9eac9f30206468f2d15302ea6649afcba1d38929afa9d1e7c12d5 + languageName: node + linkType: hard + "@grpc/grpc-js@npm:~1.10.3": version: 1.10.8 resolution: "@grpc/grpc-js@npm:1.10.8" @@ -7321,8 +7338,8 @@ __metadata: "@ethersproject/abi": "npm:*" "@ethersproject/providers": "npm:*" "@hyperlane-xyz/registry": "npm:7.1.0" - "@hyperlane-xyz/sdk": "npm:8.2.0" - "@hyperlane-xyz/utils": "npm:8.2.0" + "@hyperlane-xyz/sdk": "npm:8.3.0" + "@hyperlane-xyz/utils": "npm:8.3.0" "@inquirer/core": "npm:9.0.10" "@inquirer/figures": "npm:1.0.5" "@inquirer/prompts": "npm:3.3.2" @@ -7361,15 +7378,16 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/core@npm:5.9.2, @hyperlane-xyz/core@workspace:solidity": +"@hyperlane-xyz/core@npm:5.10.0, @hyperlane-xyz/core@workspace:solidity": version: 0.0.0-use.local resolution: "@hyperlane-xyz/core@workspace:solidity" dependencies: "@arbitrum/nitro-contracts": "npm:^1.2.1" "@eth-optimism/contracts": "npm:^0.6.0" - "@hyperlane-xyz/utils": "npm:8.2.0" + "@hyperlane-xyz/utils": "npm:8.3.0" "@layerzerolabs/lz-evm-oapp-v2": "npm:2.0.2" "@layerzerolabs/solidity-examples": "npm:^1.1.0" + "@matterlabs/hardhat-zksync-solc": "npm:^1.2.4" "@nomiclabs/hardhat-ethers": "npm:^2.2.3" "@nomiclabs/hardhat-waffle": "npm:^2.0.6" "@openzeppelin/contracts": "npm:^4.9.3" @@ -7396,6 +7414,7 @@ __metadata: tsx: "npm:^4.19.1" typechain: "patch:typechain@npm%3A8.3.2#~/.yarn/patches/typechain-npm-8.3.2-b02e27439e.patch" typescript: "npm:5.3.3" + zksync-ethers: "npm:^5.10.0" peerDependencies: "@ethersproject/abi": "*" "@ethersproject/providers": "*" @@ -7418,14 +7437,14 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/helloworld@npm:8.2.0, @hyperlane-xyz/helloworld@workspace:typescript/helloworld": +"@hyperlane-xyz/helloworld@npm:8.3.0, @hyperlane-xyz/helloworld@workspace:typescript/helloworld": version: 0.0.0-use.local resolution: "@hyperlane-xyz/helloworld@workspace:typescript/helloworld" dependencies: "@eslint/js": "npm:^9.15.0" - "@hyperlane-xyz/core": "npm:5.9.2" + "@hyperlane-xyz/core": "npm:5.10.0" "@hyperlane-xyz/registry": "npm:7.1.0" - "@hyperlane-xyz/sdk": "npm:8.2.0" + "@hyperlane-xyz/sdk": "npm:8.3.0" "@nomiclabs/hardhat-ethers": "npm:^2.2.3" "@nomiclabs/hardhat-waffle": "npm:^2.0.6" "@openzeppelin/contracts-upgradeable": "npm:^4.9.3" @@ -7474,10 +7493,10 @@ __metadata: "@ethersproject/hardware-wallets": "npm:^5.7.0" "@ethersproject/providers": "npm:*" "@google-cloud/secret-manager": "npm:^5.5.0" - "@hyperlane-xyz/helloworld": "npm:8.2.0" + "@hyperlane-xyz/helloworld": "npm:8.3.0" "@hyperlane-xyz/registry": "npm:7.1.0" - "@hyperlane-xyz/sdk": "npm:8.2.0" - "@hyperlane-xyz/utils": "npm:8.2.0" + "@hyperlane-xyz/sdk": "npm:8.3.0" + "@hyperlane-xyz/utils": "npm:8.3.0" "@inquirer/prompts": "npm:3.3.2" "@nomiclabs/hardhat-ethers": "npm:^2.2.3" "@nomiclabs/hardhat-etherscan": "npm:^3.0.3" @@ -7547,7 +7566,7 @@ __metadata: languageName: node linkType: hard -"@hyperlane-xyz/sdk@npm:8.2.0, @hyperlane-xyz/sdk@workspace:typescript/sdk": +"@hyperlane-xyz/sdk@npm:8.3.0, @hyperlane-xyz/sdk@workspace:typescript/sdk": version: 0.0.0-use.local resolution: "@hyperlane-xyz/sdk@workspace:typescript/sdk" dependencies: @@ -7557,13 +7576,13 @@ __metadata: "@cosmjs/cosmwasm-stargate": "npm:^0.32.4" "@cosmjs/stargate": "npm:^0.32.4" "@eslint/js": "npm:^9.15.0" - "@hyperlane-xyz/core": "npm:5.9.2" - "@hyperlane-xyz/utils": "npm:8.2.0" + "@hyperlane-xyz/core": "npm:5.10.0" + "@hyperlane-xyz/utils": "npm:8.3.0" "@nomiclabs/hardhat-ethers": "npm:^2.2.3" "@nomiclabs/hardhat-waffle": "npm:^2.0.6" "@safe-global/api-kit": "npm:1.3.0" "@safe-global/protocol-kit": "npm:1.3.0" - "@safe-global/safe-deployments": "npm:1.37.8" + "@safe-global/safe-deployments": "npm:1.37.23" "@solana/spl-token": "npm:^0.4.9" "@solana/web3.js": "npm:^1.95.4" "@types/mocha": "npm:^10.0.1" @@ -7601,7 +7620,7 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/utils@npm:8.2.0, @hyperlane-xyz/utils@workspace:typescript/utils": +"@hyperlane-xyz/utils@npm:8.3.0, @hyperlane-xyz/utils@workspace:typescript/utils": version: 0.0.0-use.local resolution: "@hyperlane-xyz/utils@workspace:typescript/utils" dependencies: @@ -7643,8 +7662,8 @@ __metadata: "@eslint/js": "npm:^9.15.0" "@headlessui/react": "npm:^2.1.8" "@hyperlane-xyz/registry": "npm:7.1.0" - "@hyperlane-xyz/sdk": "npm:8.2.0" - "@hyperlane-xyz/utils": "npm:8.2.0" + "@hyperlane-xyz/sdk": "npm:8.3.0" + "@hyperlane-xyz/utils": "npm:8.3.0" "@interchain-ui/react": "npm:^1.23.28" "@rainbow-me/rainbowkit": "npm:^2.2.0" "@solana/wallet-adapter-react": "npm:^0.15.32" @@ -8742,6 +8761,27 @@ __metadata: languageName: node linkType: hard +"@matterlabs/hardhat-zksync-solc@npm:^1.2.4": + version: 1.2.5 + resolution: "@matterlabs/hardhat-zksync-solc@npm:1.2.5" + dependencies: + "@nomiclabs/hardhat-docker": "npm:^2.0.2" + chai: "npm:^4.3.4" + chalk: "npm:^4.1.2" + debug: "npm:^4.3.5" + dockerode: "npm:^4.0.2" + fs-extra: "npm:^11.2.0" + proper-lockfile: "npm:^4.1.2" + semver: "npm:^7.6.2" + sinon: "npm:^18.0.0" + sinon-chai: "npm:^3.7.0" + undici: "npm:^6.18.2" + peerDependencies: + hardhat: ^2.22.5 + checksum: 10/0452ad5504258fad2f2d10be40cc79bb0e65d3470af75f70f60e4a94bb92f238031a13587cda51a0dfd77ef1cb028145088e2628a02db6d7a5ac721bf2816bf0 + languageName: node + linkType: hard + "@mdx-js/react@npm:^2.1.5": version: 2.3.0 resolution: "@mdx-js/react@npm:2.3.0" @@ -9627,6 +9667,17 @@ __metadata: languageName: node linkType: hard +"@nomiclabs/hardhat-docker@npm:^2.0.2": + version: 2.0.2 + resolution: "@nomiclabs/hardhat-docker@npm:2.0.2" + dependencies: + dockerode: "npm:^2.5.8" + fs-extra: "npm:^7.0.1" + node-fetch: "npm:^2.6.0" + checksum: 10/2b6601a7bcac115a24dc4d2ce35b76b1748ffaebd723afad17e8f506231e1d6c7e5c9df73b29d429c5eb01cb0e11ff92f10c746ca31343b0fd3ddc449c9ec8f3 + languageName: node + linkType: hard + "@nomiclabs/hardhat-ethers@npm:^2.2.3": version: 2.2.3 resolution: "@nomiclabs/hardhat-ethers@npm:2.2.3" @@ -12754,12 +12805,12 @@ __metadata: languageName: node linkType: hard -"@safe-global/safe-deployments@npm:1.37.8": - version: 1.37.8 - resolution: "@safe-global/safe-deployments@npm:1.37.8" +"@safe-global/safe-deployments@npm:1.37.23": + version: 1.37.23 + resolution: "@safe-global/safe-deployments@npm:1.37.23" dependencies: semver: "npm:^7.6.2" - checksum: 10/bc8fce2c4d557e547a6cceebb611f9584d998dfb459cd50cf338409de986bed247ebca9425b0984a6e1a6accab42c7c4d1c68811e09cc981756183ba50a5e5a9 + checksum: 10/90fca085c94fdeed7d2112699dfe58e1b1178358ccaf98049fd1fdd52be78de261753d2abc7351d6a9f977e400706d960ca6ad3f66413847a7b268999be0eff0 languageName: node linkType: hard @@ -12975,7 +13026,7 @@ __metadata: languageName: node linkType: hard -"@sinonjs/commons@npm:^3.0.0": +"@sinonjs/commons@npm:^3.0.0, @sinonjs/commons@npm:^3.0.1": version: 3.0.1 resolution: "@sinonjs/commons@npm:3.0.1" dependencies: @@ -12984,6 +13035,15 @@ __metadata: languageName: node linkType: hard +"@sinonjs/fake-timers@npm:11.2.2": + version: 11.2.2 + resolution: "@sinonjs/fake-timers@npm:11.2.2" + dependencies: + "@sinonjs/commons": "npm:^3.0.0" + checksum: 10/da7dfa677b2362bc5a321fc1563184755b5c62fbb1a72457fb9e901cd187ba9dc834f9e8a0fb5a4e1d1e6e6ad4c5b54e90900faa44dd6c82d3c49c92ec23ecd4 + languageName: node + linkType: hard + "@sinonjs/fake-timers@npm:>=5, @sinonjs/fake-timers@npm:^9.1.2": version: 9.1.2 resolution: "@sinonjs/fake-timers@npm:9.1.2" @@ -13002,6 +13062,15 @@ __metadata: languageName: node linkType: hard +"@sinonjs/fake-timers@npm:^13.0.1": + version: 13.0.5 + resolution: "@sinonjs/fake-timers@npm:13.0.5" + dependencies: + "@sinonjs/commons": "npm:^3.0.1" + checksum: 10/11ee417968fc4dce1896ab332ac13f353866075a9d2a88ed1f6258f17cc4f7d93e66031b51fcddb8c203aa4d53fd980b0ae18aba06269f4682164878a992ec3f + languageName: node + linkType: hard + "@sinonjs/samsam@npm:^6.1.1": version: 6.1.1 resolution: "@sinonjs/samsam@npm:6.1.1" @@ -13013,6 +13082,17 @@ __metadata: languageName: node linkType: hard +"@sinonjs/samsam@npm:^8.0.0": + version: 8.0.2 + resolution: "@sinonjs/samsam@npm:8.0.2" + dependencies: + "@sinonjs/commons": "npm:^3.0.1" + lodash.get: "npm:^4.4.2" + type-detect: "npm:^4.1.0" + checksum: 10/58ca9752e8e835a09ed275f8edf8da2720fe95c0c02f6bcb90ad7f86fdceb393f35f744194b705dd94216228646ec0aedbb814e245eb869b940dcf1266b7a533 + languageName: node + linkType: hard + "@sinonjs/text-encoding@npm:^0.7.1": version: 0.7.1 resolution: "@sinonjs/text-encoding@npm:0.7.1" @@ -13020,6 +13100,13 @@ __metadata: languageName: node linkType: hard +"@sinonjs/text-encoding@npm:^0.7.3": + version: 0.7.3 + resolution: "@sinonjs/text-encoding@npm:0.7.3" + checksum: 10/f0cc89bae36e7ce159187dece7800b78831288f1913e9ae8cf8a878da5388232d2049740f6f4a43ec4b43b8ad1beb55f919f45eb9a577adb4a2a6eacb27b25fc + languageName: node + linkType: hard + "@smithy/abort-controller@npm:^3.0.0": version: 3.0.0 resolution: "@smithy/abort-controller@npm:3.0.0" @@ -17079,6 +17166,18 @@ __metadata: languageName: node linkType: hard +"JSONStream@npm:1.3.2": + version: 1.3.2 + resolution: "JSONStream@npm:1.3.2" + dependencies: + jsonparse: "npm:^1.2.0" + through: "npm:>=2.2.7 <3" + bin: + JSONStream: ./bin.js + checksum: 10/3a1274f39e9b0369da5d5536906b527113326434a43b92923ac2d3c2d449009253b245055de2633b1d9ca7ae30054b6091d755e79f0cb1c7dab9b6b253871812 + languageName: node + linkType: hard + "JSONStream@npm:^1.3.5": version: 1.3.5 resolution: "JSONStream@npm:1.3.5" @@ -17903,7 +18002,7 @@ __metadata: languageName: node linkType: hard -"asn1@npm:~0.2.3": +"asn1@npm:^0.2.6, asn1@npm:~0.2.3": version: 0.2.6 resolution: "asn1@npm:0.2.6" dependencies: @@ -18317,7 +18416,7 @@ __metadata: languageName: node linkType: hard -"bcrypt-pbkdf@npm:^1.0.0": +"bcrypt-pbkdf@npm:^1.0.0, bcrypt-pbkdf@npm:^1.0.2": version: 1.0.2 resolution: "bcrypt-pbkdf@npm:1.0.2" dependencies: @@ -18485,6 +18584,16 @@ __metadata: languageName: node linkType: hard +"bl@npm:^1.0.0": + version: 1.2.3 + resolution: "bl@npm:1.2.3" + dependencies: + readable-stream: "npm:^2.3.5" + safe-buffer: "npm:^5.1.1" + checksum: 10/11d775b09ebd7d8c0df1ed7efd03cc8a2b1283c804a55153c81a0b586728a085fa24240647cac9a60163eb6f36a28cf8c45b80bf460a46336d4c84c40205faff + languageName: node + linkType: hard + "bl@npm:^4.0.3, bl@npm:^4.1.0": version: 4.1.0 resolution: "bl@npm:4.1.0" @@ -18809,6 +18918,23 @@ __metadata: languageName: node linkType: hard +"buffer-alloc-unsafe@npm:^1.1.0": + version: 1.1.0 + resolution: "buffer-alloc-unsafe@npm:1.1.0" + checksum: 10/c5e18bf51f67754ec843c9af3d4c005051aac5008a3992938dda1344e5cfec77c4b02b4ca303644d1e9a6e281765155ce6356d85c6f5ccc5cd21afc868def396 + languageName: node + linkType: hard + +"buffer-alloc@npm:^1.2.0": + version: 1.2.0 + resolution: "buffer-alloc@npm:1.2.0" + dependencies: + buffer-alloc-unsafe: "npm:^1.1.0" + buffer-fill: "npm:^1.0.0" + checksum: 10/560cd27f3cbe73c614867da373407d4506309c62fe18de45a1ce191f3785ec6ca2488d802ff82065798542422980ca25f903db078c57822218182c37c3576df5 + languageName: node + linkType: hard + "buffer-crc32@npm:~0.2.3": version: 0.2.13 resolution: "buffer-crc32@npm:0.2.13" @@ -18823,6 +18949,13 @@ __metadata: languageName: node linkType: hard +"buffer-fill@npm:^1.0.0": + version: 1.0.0 + resolution: "buffer-fill@npm:1.0.0" + checksum: 10/c29b4723ddeab01e74b5d3b982a0c6828f2ded49cef049ddca3dac661c874ecdbcecb5dd8380cf0f4adbeb8cff90a7de724126750a1f1e5ebd4eb6c59a1315b1 + languageName: node + linkType: hard + "buffer-from@npm:^1.0.0": version: 1.1.2 resolution: "buffer-from@npm:1.1.2" @@ -18928,6 +19061,13 @@ __metadata: languageName: node linkType: hard +"buildcheck@npm:~0.0.6": + version: 0.0.6 + resolution: "buildcheck@npm:0.0.6" + checksum: 10/194ee8d3b0926fd6f3e799732130ad7ab194882c56900b8670ad43c81326f64871f49b7d9f1e9baad91ca3070eb4e8b678797fe9ae78cf87dde86d8916eb25d2 + languageName: node + linkType: hard + "busboy@npm:^1.6.0": version: 1.6.0 resolution: "busboy@npm:1.6.0" @@ -19386,7 +19526,7 @@ __metadata: languageName: node linkType: hard -"chownr@npm:^1.1.1, chownr@npm:^1.1.4": +"chownr@npm:^1.0.1, chownr@npm:^1.1.1, chownr@npm:^1.1.4": version: 1.1.4 resolution: "chownr@npm:1.1.4" checksum: 10/115648f8eb38bac5e41c3857f3e663f9c39ed6480d1349977c4d96c95a47266fcacc5a5aabf3cb6c481e22d72f41992827db47301851766c4fd77ac21a4f081d @@ -19921,7 +20061,7 @@ __metadata: languageName: node linkType: hard -"concat-stream@npm:^1.6.0, concat-stream@npm:^1.6.2": +"concat-stream@npm:^1.6.0, concat-stream@npm:^1.6.2, concat-stream@npm:~1.6.2": version: 1.6.2 resolution: "concat-stream@npm:1.6.2" dependencies: @@ -20150,6 +20290,17 @@ __metadata: languageName: node linkType: hard +"cpu-features@npm:~0.0.10": + version: 0.0.10 + resolution: "cpu-features@npm:0.0.10" + dependencies: + buildcheck: "npm:~0.0.6" + nan: "npm:^2.19.0" + node-gyp: "npm:latest" + checksum: 10/941b828ffe77582b2bdc03e894c913e2e2eeb5c6043ccb01338c34446d026f6888dc480ecb85e684809f9c3889d245f3648c7907eb61a92bdfc6aed039fcda8d + languageName: node + linkType: hard + "crc-32@npm:^1.2.0": version: 1.2.2 resolution: "crc-32@npm:1.2.2" @@ -20479,7 +20630,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:^3.1.0, debug@npm:^3.2.7": +"debug@npm:^3.1.0, debug@npm:^3.2.6, debug@npm:^3.2.7": version: 3.2.7 resolution: "debug@npm:3.2.7" dependencies: @@ -20941,6 +21092,13 @@ __metadata: languageName: node linkType: hard +"diff@npm:^5.2.0": + version: 5.2.0 + resolution: "diff@npm:5.2.0" + checksum: 10/01b7b440f83a997350a988e9d2f558366c0f90f15be19f4aa7f1bb3109a4e153dfc3b9fbf78e14ea725717017407eeaa2271e3896374a0181e8f52445740846d + languageName: node + linkType: hard + "difflib@npm:^0.2.4": version: 0.2.4 resolution: "difflib@npm:0.2.4" @@ -20973,6 +21131,56 @@ __metadata: languageName: node linkType: hard +"docker-modem@npm:^1.0.8": + version: 1.0.9 + resolution: "docker-modem@npm:1.0.9" + dependencies: + JSONStream: "npm:1.3.2" + debug: "npm:^3.2.6" + readable-stream: "npm:~1.0.26-4" + split-ca: "npm:^1.0.0" + checksum: 10/2ade3d9f1b25231a5ecadcbfb9401a397eff3de2eec7add8130de1c40004faaa58fe074e5110ccef12957973089e5911b711648c77944a4a15d908e9b9605549 + languageName: node + linkType: hard + +"docker-modem@npm:^5.0.6": + version: 5.0.6 + resolution: "docker-modem@npm:5.0.6" + dependencies: + debug: "npm:^4.1.1" + readable-stream: "npm:^3.5.0" + split-ca: "npm:^1.0.1" + ssh2: "npm:^1.15.0" + checksum: 10/4977797814c29205f0762215f2e3e26600986bb65139018ff6840ff4c596e5d19f3002be1abcc5e73e3828870bb73bab28275a6458ad027ed56ab61fca014b6d + languageName: node + linkType: hard + +"dockerode@npm:^2.5.8": + version: 2.5.8 + resolution: "dockerode@npm:2.5.8" + dependencies: + concat-stream: "npm:~1.6.2" + docker-modem: "npm:^1.0.8" + tar-fs: "npm:~1.16.3" + checksum: 10/13111cfcaf47905cd2cd323a07cb5b79404ef5e9032e33ef3a6f71d1f72283d9b2921b6de955c8454b147bbf4db33822a80d960b2250e3e8aed62ffe0b43083f + languageName: node + linkType: hard + +"dockerode@npm:^4.0.2": + version: 4.0.4 + resolution: "dockerode@npm:4.0.4" + dependencies: + "@balena/dockerignore": "npm:^1.0.2" + "@grpc/grpc-js": "npm:^1.11.1" + "@grpc/proto-loader": "npm:^0.7.13" + docker-modem: "npm:^5.0.6" + protobufjs: "npm:^7.3.2" + tar-fs: "npm:~2.0.1" + uuid: "npm:^10.0.0" + checksum: 10/db2304e6125d0c4246833eaa6a389497c98564ba2ed18fa465eace1b6eb6c2a41f1711fc1e57cd2fc0f7ca6be80eeca43b0c35cd6b86205e34faf9acb0f72bcc + languageName: node + linkType: hard + "doctrine@npm:^2.1.0": version: 2.1.0 resolution: "doctrine@npm:2.1.0" @@ -23672,6 +23880,17 @@ __metadata: languageName: node linkType: hard +"fs-extra@npm:^11.2.0": + version: 11.3.0 + resolution: "fs-extra@npm:11.3.0" + dependencies: + graceful-fs: "npm:^4.2.0" + jsonfile: "npm:^6.0.1" + universalify: "npm:^2.0.0" + checksum: 10/c9fe7b23dded1efe7bbae528d685c3206477e20cc60e9aaceb3f024f9b9ff2ee1f62413c161cb88546cc564009ab516dec99e9781ba782d869bb37e4fe04a97f + languageName: node + linkType: hard + "fs-extra@npm:^4.0.2": version: 4.0.3 resolution: "fs-extra@npm:4.0.3" @@ -27284,6 +27503,13 @@ __metadata: languageName: node linkType: hard +"just-extend@npm:^6.2.0": + version: 6.2.0 + resolution: "just-extend@npm:6.2.0" + checksum: 10/1f487b074b9e5773befdd44dc5d1b446f01f24f7d4f1f255d51c0ef7f686e8eb5f95d983b792b9ca5c8b10cd7e60a924d64103725759eddbd7f18bcb22743f92 + languageName: node + linkType: hard + "jwa@npm:^2.0.0": version: 2.0.0 resolution: "jwa@npm:2.0.0" @@ -29112,7 +29338,7 @@ __metadata: languageName: node linkType: hard -"nan@npm:^2.13.2": +"nan@npm:^2.13.2, nan@npm:^2.19.0, nan@npm:^2.20.0": version: 2.22.0 resolution: "nan@npm:2.22.0" dependencies: @@ -29224,6 +29450,19 @@ __metadata: languageName: node linkType: hard +"nise@npm:^6.0.0": + version: 6.1.1 + resolution: "nise@npm:6.1.1" + dependencies: + "@sinonjs/commons": "npm:^3.0.1" + "@sinonjs/fake-timers": "npm:^13.0.1" + "@sinonjs/text-encoding": "npm:^0.7.3" + just-extend: "npm:^6.2.0" + path-to-regexp: "npm:^8.1.0" + checksum: 10/2d3175587cf0a351e2c91eb643fdc59d266de39f394a3ac0bace38571749d1e7f25341d763899245139b8f0d2ee048b2d3387d75ecf94c4897e947d5fc881eea + languageName: node + linkType: hard + "nock@npm:13.5.4": version: 13.5.4 resolution: "nock@npm:13.5.4" @@ -30362,6 +30601,13 @@ __metadata: languageName: node linkType: hard +"path-to-regexp@npm:^8.1.0": + version: 8.2.0 + resolution: "path-to-regexp@npm:8.2.0" + checksum: 10/23378276a172b8ba5f5fb824475d1818ca5ccee7bbdb4674701616470f23a14e536c1db11da9c9e6d82b82c556a817bbf4eee6e41b9ed20090ef9427cbb38e13 + languageName: node + linkType: hard + "path-type@npm:^4.0.0": version: 4.0.0 resolution: "path-type@npm:4.0.0" @@ -31016,7 +31262,7 @@ __metadata: languageName: node linkType: hard -"proper-lockfile@npm:^4.1.1": +"proper-lockfile@npm:^4.1.1, proper-lockfile@npm:^4.1.2": version: 4.1.2 resolution: "proper-lockfile@npm:4.1.2" dependencies: @@ -31107,6 +31353,26 @@ __metadata: languageName: node linkType: hard +"protobufjs@npm:^7.3.2": + version: 7.4.0 + resolution: "protobufjs@npm:7.4.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: 10/408423506610f70858d7593632f4a6aa4f05796c90fd632be9b9252457c795acc71aa6d3b54bb7f48a890141728fee4ca3906723ccea6c202ad71f21b3879b8b + languageName: node + linkType: hard + "proxy-addr@npm:~2.0.7": version: 2.0.7 resolution: "proxy-addr@npm:2.0.7" @@ -31159,6 +31425,16 @@ __metadata: languageName: node linkType: hard +"pump@npm:^1.0.0": + version: 1.0.3 + resolution: "pump@npm:1.0.3" + dependencies: + end-of-stream: "npm:^1.1.0" + once: "npm:^1.3.1" + checksum: 10/61fe58694f9900020a5cf5bc765d74396891c201afecf06659df2f5874fd832be4e19e2f95cc72d8b9eb98ace0a4db3cebf7343f9fc893a930577be29e3ad8b5 + languageName: node + linkType: hard + "pump@npm:^2.0.0": version: 2.0.1 resolution: "pump@npm:2.0.1" @@ -31889,7 +32165,7 @@ __metadata: languageName: node linkType: hard -"readable-stream@npm:^2.0.0, readable-stream@npm:^2.3.3, readable-stream@npm:~2.3.6": +"readable-stream@npm:^2.0.0, readable-stream@npm:^2.3.0, readable-stream@npm:^2.3.3, readable-stream@npm:^2.3.5, readable-stream@npm:~2.3.6": version: 2.3.8 resolution: "readable-stream@npm:2.3.8" dependencies: @@ -31919,7 +32195,7 @@ __metadata: languageName: node linkType: hard -"readable-stream@npm:^3.1.0, readable-stream@npm:^3.6.2": +"readable-stream@npm:^3.1.0, readable-stream@npm:^3.5.0, readable-stream@npm:^3.6.2": version: 3.6.2 resolution: "readable-stream@npm:3.6.2" dependencies: @@ -31954,6 +32230,18 @@ __metadata: languageName: node linkType: hard +"readable-stream@npm:~1.0.26-4": + version: 1.0.34 + resolution: "readable-stream@npm:1.0.34" + dependencies: + core-util-is: "npm:~1.0.0" + inherits: "npm:~2.0.1" + isarray: "npm:0.0.1" + string_decoder: "npm:~0.10.x" + checksum: 10/20537fca5a8ffd4af0f483be1cce0e981ed8cbb1087e0c762e2e92ae77f1005627272cebed8422f28047b465056aa1961fefd24baf532ca6a3616afea6811ae0 + languageName: node + linkType: hard + "readdirp@npm:~3.2.0": version: 3.2.0 resolution: "readdirp@npm:3.2.0" @@ -33398,6 +33686,16 @@ __metadata: languageName: node linkType: hard +"sinon-chai@npm:^3.7.0": + version: 3.7.0 + resolution: "sinon-chai@npm:3.7.0" + peerDependencies: + chai: ^4.0.0 + sinon: ">=4.0.0" + checksum: 10/028853eb8a545ca613c6863014a40f07d1e6b81467e20939fefcd13f170206d24165b91099fb297aeb4d137745e321da25daa8e2d665cc0a78f90d5b877e8bbe + languageName: node + linkType: hard + "sinon@npm:^13.0.2": version: 13.0.2 resolution: "sinon@npm:13.0.2" @@ -33412,6 +33710,20 @@ __metadata: languageName: node linkType: hard +"sinon@npm:^18.0.0": + version: 18.0.1 + resolution: "sinon@npm:18.0.1" + dependencies: + "@sinonjs/commons": "npm:^3.0.1" + "@sinonjs/fake-timers": "npm:11.2.2" + "@sinonjs/samsam": "npm:^8.0.0" + diff: "npm:^5.2.0" + nise: "npm:^6.0.0" + supports-color: "npm:^7" + checksum: 10/65be65a7c5dbef7e9e315820bcd17fe0387fb07cb5dd41d3d6a89177e9a2ca463144676d8407348dc075758daa7cf37b9376d35bd1bc7e06717e87d03aa26111 + languageName: node + linkType: hard + "sisteransi@npm:^1.0.5": version: 1.0.5 resolution: "sisteransi@npm:1.0.5" @@ -33911,6 +34223,13 @@ __metadata: languageName: node linkType: hard +"split-ca@npm:^1.0.0, split-ca@npm:^1.0.1": + version: 1.0.1 + resolution: "split-ca@npm:1.0.1" + checksum: 10/1e7409938a95ee843fe2593156a5735e6ee63772748ee448ea8477a5a3e3abde193c3325b3696e56a5aff07c7dcf6b1f6a2f2a036895b4f3afe96abb366d893f + languageName: node + linkType: hard + "split-on-first@npm:^1.0.0": version: 1.1.0 resolution: "split-on-first@npm:1.1.0" @@ -33932,6 +34251,23 @@ __metadata: languageName: node linkType: hard +"ssh2@npm:^1.15.0": + version: 1.16.0 + resolution: "ssh2@npm:1.16.0" + dependencies: + asn1: "npm:^0.2.6" + bcrypt-pbkdf: "npm:^1.0.2" + cpu-features: "npm:~0.0.10" + nan: "npm:^2.20.0" + dependenciesMeta: + cpu-features: + optional: true + nan: + optional: true + checksum: 10/0951c22d9c5a0e3b89a8e5ae890ebcbce9f1f94dbed37d1490e4e48e26bc8b074fa81f202ee57b708e31b5f33033f4c870b92047f4f02b6bc26c32225b01d84c + languageName: node + linkType: hard + "sshpk@npm:^1.7.0": version: 1.17.0 resolution: "sshpk@npm:1.17.0" @@ -34330,6 +34666,13 @@ __metadata: languageName: node linkType: hard +"string_decoder@npm:~0.10.x": + version: 0.10.31 + resolution: "string_decoder@npm:0.10.31" + checksum: 10/cc43e6b1340d4c7843da0e37d4c87a4084c2342fc99dcf6563c3ec273bb082f0cbd4ebf25d5da19b04fb16400d393885fda830be5128e1c416c73b5a6165f175 + languageName: node + linkType: hard + "string_decoder@npm:~1.1.1": version: 1.1.1 resolution: "string_decoder@npm:1.1.1" @@ -34567,7 +34910,7 @@ __metadata: languageName: node linkType: hard -"supports-color@npm:^7.0.0, supports-color@npm:^7.1.0, supports-color@npm:^7.2.0": +"supports-color@npm:^7, supports-color@npm:^7.0.0, supports-color@npm:^7.1.0, supports-color@npm:^7.2.0": version: 7.2.0 resolution: "supports-color@npm:7.2.0" dependencies: @@ -34794,7 +35137,46 @@ __metadata: languageName: node linkType: hard -"tar-stream@npm:^2.1.4": +"tar-fs@npm:~1.16.3": + version: 1.16.4 + resolution: "tar-fs@npm:1.16.4" + dependencies: + chownr: "npm:^1.0.1" + mkdirp: "npm:^0.5.1" + pump: "npm:^1.0.0" + tar-stream: "npm:^1.1.2" + checksum: 10/2392fb92ebcdbf1b70961192a0f32955dbfc5cd844d4980e7e0dc7b8cb269d6e23831ae8ceb8137322367dcc42ae946f2349d0fd79b89b3c30b0a7d3ff5fc15e + languageName: node + linkType: hard + +"tar-fs@npm:~2.0.1": + version: 2.0.1 + resolution: "tar-fs@npm:2.0.1" + dependencies: + chownr: "npm:^1.1.1" + mkdirp-classic: "npm:^0.5.2" + pump: "npm:^3.0.0" + tar-stream: "npm:^2.0.0" + checksum: 10/85ceac6fce0e9175b5b67c0eca8864b7d29a940cae8b7657c60b66e8a252319d701c3df12814162a6839e6120f9e1975757293bdeaf294ad5b15721d236c4d32 + languageName: node + linkType: hard + +"tar-stream@npm:^1.1.2": + version: 1.6.2 + resolution: "tar-stream@npm:1.6.2" + dependencies: + bl: "npm:^1.0.0" + buffer-alloc: "npm:^1.2.0" + end-of-stream: "npm:^1.0.0" + fs-constants: "npm:^1.0.0" + readable-stream: "npm:^2.3.0" + to-buffer: "npm:^1.1.1" + xtend: "npm:^4.0.0" + checksum: 10/ac9b850bd40e6d4b251abcf92613bafd9fc9e592c220c781ebcdbb0ba76da22a245d9ea3ea638ad7168910e7e1ae5079333866cd679d2f1ffadb99c403f99d7f + languageName: node + linkType: hard + +"tar-stream@npm:^2.0.0, tar-stream@npm:^2.1.4": version: 2.2.0 resolution: "tar-stream@npm:2.2.0" dependencies: @@ -35096,6 +35478,13 @@ __metadata: languageName: node linkType: hard +"to-buffer@npm:^1.1.1": + version: 1.1.1 + resolution: "to-buffer@npm:1.1.1" + checksum: 10/8ade59fe04239b281496b6067bc83ad0371a3657552276cbd09ffffaeb3ad0018a28306d61b854b83280eabe1829cbc53001ccd761e834c6062cbcc7fee2766a + languageName: node + linkType: hard + "to-fast-properties@npm:^2.0.0": version: 2.0.0 resolution: "to-fast-properties@npm:2.0.0" @@ -36358,6 +36747,15 @@ __metadata: languageName: node linkType: hard +"uuid@npm:^10.0.0": + version: 10.0.0 + resolution: "uuid@npm:10.0.0" + bin: + uuid: dist/bin/uuid + checksum: 10/35aa60614811a201ff90f8ca5e9ecb7076a75c3821e17f0f5ff72d44e36c2d35fcbc2ceee9c4ac7317f4cc41895da30e74f3885e30313bee48fda6338f250538 + languageName: node + linkType: hard + "uuid@npm:^3.3.2": version: 3.4.0 resolution: "uuid@npm:3.4.0"