From a29a899a688b2209e0fcd7fda25a39c3a73fa826 Mon Sep 17 00:00:00 2001 From: Daniel Savu <23065004+daniel-savu@users.noreply.github.com> Date: Tue, 10 Dec 2024 12:34:17 +0000 Subject: [PATCH 01/18] chore: bump escalator version (#4978) ### Description the escalator doesn't log the hash of the new gas-escalated transaction, so it's hard to find the transaction that actually landed onchain (you have to look by nonce) - example [here](https://cloudlogging.app.goo.gl/cFXYHui4agpxU4AK9). This PR uses a newer version of the escalator, which does log the hash. ethers-rs fork PR: https://github.com/hyperlane-xyz/ethers-rs/pull/22 --- rust/main/Cargo.lock | 20 ++++++++++---------- rust/main/Cargo.toml | 10 +++++----- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/rust/main/Cargo.lock b/rust/main/Cargo.lock index e8831b6029..110954d2f7 100644 --- a/rust/main/Cargo.lock +++ b/rust/main/Cargo.lock @@ -2901,7 +2901,7 @@ dependencies = [ [[package]] name = "ethers" version = "1.0.2" -source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2024-12-03-3#edf703a6e515266245b88bb4f1daad24f7c6566c" +source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2024-12-10#d9f822ef9dd3d63b88cae74973540ef9e6773015" dependencies = [ "ethers-addressbook", "ethers-contract", @@ -2915,7 +2915,7 @@ dependencies = [ [[package]] name = "ethers-addressbook" version = "1.0.2" -source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2024-12-03-3#edf703a6e515266245b88bb4f1daad24f7c6566c" +source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2024-12-10#d9f822ef9dd3d63b88cae74973540ef9e6773015" dependencies = [ "ethers-core", "once_cell", @@ -2926,7 +2926,7 @@ dependencies = [ [[package]] name = "ethers-contract" version = "1.0.2" -source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2024-12-03-3#edf703a6e515266245b88bb4f1daad24f7c6566c" +source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2024-12-10#d9f822ef9dd3d63b88cae74973540ef9e6773015" dependencies = [ "ethers-contract-abigen", "ethers-contract-derive", @@ -2944,7 +2944,7 @@ dependencies = [ [[package]] name = "ethers-contract-abigen" version = "1.0.2" -source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2024-12-03-3#edf703a6e515266245b88bb4f1daad24f7c6566c" +source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2024-12-10#d9f822ef9dd3d63b88cae74973540ef9e6773015" dependencies = [ "Inflector", "cfg-if", @@ -2968,7 +2968,7 @@ dependencies = [ [[package]] name = "ethers-contract-derive" version = "1.0.2" -source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2024-12-03-3#edf703a6e515266245b88bb4f1daad24f7c6566c" +source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2024-12-10#d9f822ef9dd3d63b88cae74973540ef9e6773015" dependencies = [ "ethers-contract-abigen", "ethers-core", @@ -2982,7 +2982,7 @@ dependencies = [ [[package]] name = "ethers-core" version = "1.0.2" -source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2024-12-03-3#edf703a6e515266245b88bb4f1daad24f7c6566c" +source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2024-12-10#d9f822ef9dd3d63b88cae74973540ef9e6773015" dependencies = [ "arrayvec", "bytes", @@ -3012,7 +3012,7 @@ dependencies = [ [[package]] name = "ethers-etherscan" version = "1.0.2" -source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2024-12-03-3#edf703a6e515266245b88bb4f1daad24f7c6566c" +source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2024-12-10#d9f822ef9dd3d63b88cae74973540ef9e6773015" dependencies = [ "ethers-core", "getrandom 0.2.15", @@ -3028,7 +3028,7 @@ dependencies = [ [[package]] name = "ethers-middleware" version = "1.0.2" -source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2024-12-03-3#edf703a6e515266245b88bb4f1daad24f7c6566c" +source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2024-12-10#d9f822ef9dd3d63b88cae74973540ef9e6773015" dependencies = [ "async-trait", "auto_impl 0.5.0", @@ -3076,7 +3076,7 @@ dependencies = [ [[package]] name = "ethers-providers" version = "1.0.2" -source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2024-12-03-3#edf703a6e515266245b88bb4f1daad24f7c6566c" +source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2024-12-10#d9f822ef9dd3d63b88cae74973540ef9e6773015" dependencies = [ "async-trait", "auto_impl 1.2.0", @@ -3112,7 +3112,7 @@ dependencies = [ [[package]] name = "ethers-signers" version = "1.0.2" -source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2024-12-03-3#edf703a6e515266245b88bb4f1daad24f7c6566c" +source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2024-12-10#d9f822ef9dd3d63b88cae74973540ef9e6773015" dependencies = [ "async-trait", "coins-bip32 0.7.0", diff --git a/rust/main/Cargo.toml b/rust/main/Cargo.toml index 3fcdfa294c..6a0b7cbb35 100644 --- a/rust/main/Cargo.toml +++ b/rust/main/Cargo.toml @@ -197,27 +197,27 @@ overflow-checks = true [workspace.dependencies.ethers] features = [] git = "https://github.com/hyperlane-xyz/ethers-rs" -tag = "2024-12-03-3" +tag = "2024-12-10" [workspace.dependencies.ethers-contract] features = ["legacy"] git = "https://github.com/hyperlane-xyz/ethers-rs" -tag = "2024-12-03-3" +tag = "2024-12-10" [workspace.dependencies.ethers-core] features = [] git = "https://github.com/hyperlane-xyz/ethers-rs" -tag = "2024-12-03-3" +tag = "2024-12-10" [workspace.dependencies.ethers-providers] features = [] git = "https://github.com/hyperlane-xyz/ethers-rs" -tag = "2024-12-03-3" +tag = "2024-12-10" [workspace.dependencies.ethers-signers] features = ["aws"] git = "https://github.com/hyperlane-xyz/ethers-rs" -tag = "2024-12-03-3" +tag = "2024-12-10" [patch.crates-io.curve25519-dalek] branch = "v3.2.2-relax-zeroize" From 79f8197f379eed08e6c0312532a8e8f17a9cb84f Mon Sep 17 00:00:00 2001 From: mshojaei-txfusion <138107084+mshojaei-txfusion@users.noreply.github.com> Date: Tue, 10 Dec 2024 18:03:37 +0330 Subject: [PATCH 02/18] feat: Multi EVM Chain Signers (#4922) ### Description This PR modifies the warp init command to read the multiProtocolSigner from the signer middleware instead of creating a new instance. ### Drive-by changes ### Related issues - https://github.com/hyperlane-xyz/hyperlane-monorepo/pull/4910 - Fixes issue with duplicate signer initialization in warp route configuration - Improves consistency with other commands that use signer middleware ### Backward compatibility Yes - This change is backward compatible as it maintains the same functionality while making the multiProtocolSigner parameter optional. ### Testing Manual testing --------- Co-authored-by: Morteza Shojaei <31728528+mortezashojaei@users.noreply.github.com> Co-authored-by: ljankovic-txfusion --- .changeset/chilly-balloons-rule.md | 5 + .changeset/spicy-gifts-hear.md | 5 + typescript/cli/cli.ts | 7 +- typescript/cli/src/commands/config.ts | 15 ++ typescript/cli/src/commands/options.ts | 5 +- typescript/cli/src/commands/signCommands.ts | 29 +- typescript/cli/src/commands/strategy.ts | 70 +++++ typescript/cli/src/config/core.ts | 4 +- typescript/cli/src/config/hooks.ts | 6 +- typescript/cli/src/config/ism.ts | 6 +- typescript/cli/src/config/strategy.ts | 186 +++++++++++++ typescript/cli/src/config/warp.ts | 15 +- typescript/cli/src/context/context.ts | 58 +++- .../strategies/chain/ChainResolverFactory.ts | 36 +++ .../strategies/chain/MultiChainResolver.ts | 249 ++++++++++++++++++ .../strategies/chain/SingleChainResolver.ts | 25 ++ .../cli/src/context/strategies/chain/types.ts | 10 + .../signer/BaseMultiProtocolSigner.ts | 22 ++ .../signer/MultiProtocolSignerFactory.ts | 79 ++++++ .../signer/MultiProtocolSignerManager.ts | 153 +++++++++++ typescript/cli/src/context/types.ts | 7 +- typescript/cli/src/deploy/agent.ts | 1 + typescript/cli/src/deploy/core.ts | 6 +- typescript/cli/src/deploy/utils.ts | 30 ++- typescript/cli/src/deploy/warp.ts | 17 +- typescript/cli/src/read/warp.ts | 12 +- typescript/cli/src/send/transfer.ts | 24 +- typescript/cli/src/tests/commands/helpers.ts | 7 + typescript/cli/src/utils/balances.ts | 5 +- typescript/cli/src/utils/chains.ts | 33 +++ typescript/cli/src/utils/output.ts | 47 ++++ .../transactions/submitter/ethersV5/types.ts | 2 + typescript/utils/src/addresses.ts | 10 +- typescript/utils/src/index.ts | 1 + 34 files changed, 1112 insertions(+), 75 deletions(-) create mode 100644 .changeset/chilly-balloons-rule.md create mode 100644 .changeset/spicy-gifts-hear.md create mode 100644 typescript/cli/src/commands/strategy.ts create mode 100644 typescript/cli/src/config/strategy.ts create mode 100644 typescript/cli/src/context/strategies/chain/ChainResolverFactory.ts create mode 100644 typescript/cli/src/context/strategies/chain/MultiChainResolver.ts create mode 100644 typescript/cli/src/context/strategies/chain/SingleChainResolver.ts create mode 100644 typescript/cli/src/context/strategies/chain/types.ts create mode 100644 typescript/cli/src/context/strategies/signer/BaseMultiProtocolSigner.ts create mode 100644 typescript/cli/src/context/strategies/signer/MultiProtocolSignerFactory.ts create mode 100644 typescript/cli/src/context/strategies/signer/MultiProtocolSignerManager.ts diff --git a/.changeset/chilly-balloons-rule.md b/.changeset/chilly-balloons-rule.md new file mode 100644 index 0000000000..b339b75699 --- /dev/null +++ b/.changeset/chilly-balloons-rule.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/utils': minor +--- + +Added `isPrivateKeyEvm` function for validating EVM private keys diff --git a/.changeset/spicy-gifts-hear.md b/.changeset/spicy-gifts-hear.md new file mode 100644 index 0000000000..37d4efa28d --- /dev/null +++ b/.changeset/spicy-gifts-hear.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/cli': minor +--- + +Added strategy management CLI commands and MultiProtocolSigner implementation for flexible cross-chain signer configuration and management diff --git a/typescript/cli/cli.ts b/typescript/cli/cli.ts index 77be0b86f8..45cad33bb8 100644 --- a/typescript/cli/cli.ts +++ b/typescript/cli/cli.ts @@ -19,15 +19,17 @@ import { overrideRegistryUriCommandOption, registryUriCommandOption, skipConfirmationOption, + strategyCommandOption, } from './src/commands/options.js'; import { registryCommand } from './src/commands/registry.js'; import { relayerCommand } from './src/commands/relayer.js'; import { sendCommand } from './src/commands/send.js'; import { statusCommand } from './src/commands/status.js'; +import { strategyCommand } from './src/commands/strategy.js'; import { submitCommand } from './src/commands/submit.js'; import { validatorCommand } from './src/commands/validator.js'; import { warpCommand } from './src/commands/warp.js'; -import { contextMiddleware } from './src/context/context.js'; +import { contextMiddleware, signerMiddleware } from './src/context/context.js'; import { configureLogger, errorRed } from './src/logger.js'; import { checkVersion } from './src/utils/version-check.js'; import { VERSION } from './src/version.js'; @@ -49,12 +51,14 @@ try { .option('key', keyCommandOption) .option('disableProxy', disableProxyCommandOption) .option('yes', skipConfirmationOption) + .option('strategy', strategyCommandOption) .global(['log', 'verbosity', 'registry', 'overrides', 'yes']) .middleware([ (argv) => { configureLogger(argv.log as LogFormat, argv.verbosity as LogLevel); }, contextMiddleware, + signerMiddleware, ]) .command(avsCommand) .command(configCommand) @@ -66,6 +70,7 @@ try { .command(relayerCommand) .command(sendCommand) .command(statusCommand) + .command(strategyCommand) .command(submitCommand) .command(validatorCommand) .command(warpCommand) diff --git a/typescript/cli/src/commands/config.ts b/typescript/cli/src/commands/config.ts index e72b72452a..4a5c6b580a 100644 --- a/typescript/cli/src/commands/config.ts +++ b/typescript/cli/src/commands/config.ts @@ -3,6 +3,7 @@ import { CommandModule } from 'yargs'; import { readChainConfigs } from '../config/chain.js'; import { readIsmConfig } from '../config/ism.js'; import { readMultisigConfig } from '../config/multisig.js'; +import { readChainSubmissionStrategyConfig } from '../config/strategy.js'; import { readWarpRouteDeployConfig } from '../config/warp.js'; import { CommandModuleWithContext } from '../context/types.js'; import { log, logGreen } from '../logger.js'; @@ -31,6 +32,7 @@ const validateCommand: CommandModule = { .command(validateChainCommand) .command(validateIsmCommand) .command(validateIsmAdvancedCommand) + .command(validateStrategyCommand) .command(validateWarpCommand) .version(false) .demandCommand(), @@ -76,6 +78,19 @@ const validateIsmAdvancedCommand: CommandModuleWithContext<{ path: string }> = { }, }; +const validateStrategyCommand: CommandModuleWithContext<{ path: string }> = { + command: 'strategy', + describe: 'Validates a Strategy config file', + builder: { + path: inputFileCommandOption(), + }, + handler: async ({ path }) => { + await readChainSubmissionStrategyConfig(path); + logGreen('Config is valid'); + process.exit(0); + }, +}; + const validateWarpCommand: CommandModuleWithContext<{ path: string }> = { command: 'warp', describe: 'Validate a Warp Route deployment config file', diff --git a/typescript/cli/src/commands/options.ts b/typescript/cli/src/commands/options.ts index f23194c804..baf0fa8472 100644 --- a/typescript/cli/src/commands/options.ts +++ b/typescript/cli/src/commands/options.ts @@ -95,6 +95,7 @@ export const DEFAULT_WARP_ROUTE_DEPLOYMENT_CONFIG_PATH = './configs/warp-route-deployment.yaml'; export const DEFAULT_CORE_DEPLOYMENT_CONFIG_PATH = './configs/core-config.yaml'; +export const DEFAULT_STRATEGY_CONFIG_PATH = `${os.homedir()}/.hyperlane/strategies/default-strategy.yaml`; export const warpDeploymentConfigCommandOption: Options = { type: 'string', @@ -196,8 +197,8 @@ export const transactionsCommandOption: Options = { export const strategyCommandOption: Options = { type: 'string', description: 'The submission strategy input file path.', - alias: 's', - demandOption: true, + alias: ['s', 'strategy'], + demandOption: false, }; export const addressCommandOption = ( diff --git a/typescript/cli/src/commands/signCommands.ts b/typescript/cli/src/commands/signCommands.ts index 37d83096a0..8cfa6d4c1a 100644 --- a/typescript/cli/src/commands/signCommands.ts +++ b/typescript/cli/src/commands/signCommands.ts @@ -1,11 +1,36 @@ // Commands that send tx and require a key to sign. // It's useful to have this listed here so the context // middleware can request keys up front when required. -export const SIGN_COMMANDS = ['deploy', 'send', 'status', 'submit', 'relayer']; +export const SIGN_COMMANDS = [ + 'apply', + 'deploy', + 'send', + 'status', + 'submit', + 'relayer', +]; export function isSignCommand(argv: any): boolean { + //TODO: fix reading and checking warp without signer, and remove this + const temporarySignCommandsCheck = + argv._[0] === 'warp' && (argv._[1] === 'read' || argv._[1] === 'check'); return ( SIGN_COMMANDS.includes(argv._[0]) || - (argv._.length > 1 && SIGN_COMMANDS.includes(argv._[1])) + (argv._.length > 1 && SIGN_COMMANDS.includes(argv._[1])) || + temporarySignCommandsCheck ); } + +export enum CommandType { + WARP_DEPLOY = 'warp:deploy', + WARP_SEND = 'warp:send', + WARP_APPLY = 'warp:apply', + WARP_READ = 'warp:read', + WARP_CHECK = 'warp:check', + SEND_MESSAGE = 'send:message', + AGENT_KURTOSIS = 'deploy:kurtosis-agents', + STATUS = 'status:', + SUBMIT = 'submit:', + RELAYER = 'relayer:', + CORE_APPLY = 'core:apply', +} diff --git a/typescript/cli/src/commands/strategy.ts b/typescript/cli/src/commands/strategy.ts new file mode 100644 index 0000000000..414a3d48ee --- /dev/null +++ b/typescript/cli/src/commands/strategy.ts @@ -0,0 +1,70 @@ +import { stringify as yamlStringify } from 'yaml'; +import { CommandModule } from 'yargs'; + +import { + createStrategyConfig, + readChainSubmissionStrategyConfig, +} from '../config/strategy.js'; +import { CommandModuleWithWriteContext } from '../context/types.js'; +import { log, logCommandHeader } from '../logger.js'; +import { indentYamlOrJson } from '../utils/files.js'; +import { maskSensitiveData } from '../utils/output.js'; + +import { + DEFAULT_STRATEGY_CONFIG_PATH, + outputFileCommandOption, + strategyCommandOption, +} from './options.js'; + +/** + * Parent command + */ +export const strategyCommand: CommandModule = { + command: 'strategy', + describe: 'Manage Hyperlane deployment strategies', + builder: (yargs) => + yargs.command(init).command(read).version(false).demandCommand(), + handler: () => log('Command required'), +}; + +export const init: CommandModuleWithWriteContext<{ + out: string; +}> = { + command: 'init', + describe: 'Creates strategy configuration', + builder: { + out: outputFileCommandOption(DEFAULT_STRATEGY_CONFIG_PATH), + }, + handler: async ({ context, out }) => { + logCommandHeader(`Hyperlane Strategy Init`); + + await createStrategyConfig({ + context, + outPath: out, + }); + process.exit(0); + }, +}; + +export const read: CommandModuleWithWriteContext<{ + strategy: string; +}> = { + command: 'read', + describe: 'Reads strategy configuration', + builder: { + strategy: { + ...strategyCommandOption, + demandOption: true, + default: DEFAULT_STRATEGY_CONFIG_PATH, + }, + }, + handler: async ({ strategy: strategyUrl }) => { + logCommandHeader(`Hyperlane Strategy Read`); + + const strategy = await readChainSubmissionStrategyConfig(strategyUrl); + const maskedConfig = maskSensitiveData(strategy); + log(indentYamlOrJson(yamlStringify(maskedConfig, null, 2), 4)); + + process.exit(0); + }, +}; diff --git a/typescript/cli/src/config/core.ts b/typescript/cli/src/config/core.ts index c7161f6b4b..5b4395c951 100644 --- a/typescript/cli/src/config/core.ts +++ b/typescript/cli/src/config/core.ts @@ -39,7 +39,7 @@ export async function createCoreDeployConfig({ logBlue('Creating a new core deployment config...'); const owner = await detectAndConfirmOrPrompt( - async () => context.signer?.getAddress(), + async () => context.signerAddress, ENTER_DESIRED_VALUE_MSG, 'owner address', SIGNER_PROMPT_LABEL, @@ -64,7 +64,7 @@ export async function createCoreDeployConfig({ }); proxyAdmin = { owner: await detectAndConfirmOrPrompt( - async () => context.signer?.getAddress(), + async () => context.signerAddress, ENTER_DESIRED_VALUE_MSG, 'ProxyAdmin owner address', SIGNER_PROMPT_LABEL, diff --git a/typescript/cli/src/config/hooks.ts b/typescript/cli/src/config/hooks.ts index e8df64dc00..5ad005dc24 100644 --- a/typescript/cli/src/config/hooks.ts +++ b/typescript/cli/src/config/hooks.ts @@ -243,10 +243,10 @@ async function getOwnerAndBeneficiary( advanced: boolean, ) { const unnormalizedOwner = - !advanced && context.signer - ? await context.signer.getAddress() + !advanced && context.signerAddress + ? context.signerAddress : await detectAndConfirmOrPrompt( - async () => context.signer?.getAddress(), + async () => context.signerAddress, `For ${module}, enter`, 'owner address', 'signer', diff --git a/typescript/cli/src/config/ism.ts b/typescript/cli/src/config/ism.ts index f7f6bab9ad..81440e96eb 100644 --- a/typescript/cli/src/config/ism.ts +++ b/typescript/cli/src/config/ism.ts @@ -163,10 +163,10 @@ export const createTrustedRelayerConfig = callWithConfigCreationLogs( advanced: boolean = false, ): Promise => { const relayer = - !advanced && context.signer - ? await context.signer.getAddress() + !advanced && context.signerAddress + ? context.signerAddress : await detectAndConfirmOrPrompt( - async () => context.signer?.getAddress(), + async () => context.signerAddress, 'For trusted relayer ISM, enter', 'relayer address', 'signer', diff --git a/typescript/cli/src/config/strategy.ts b/typescript/cli/src/config/strategy.ts new file mode 100644 index 0000000000..f57c7d3378 --- /dev/null +++ b/typescript/cli/src/config/strategy.ts @@ -0,0 +1,186 @@ +import { confirm, input, password, select } from '@inquirer/prompts'; +import { Wallet } from 'ethers'; +import { stringify as yamlStringify } from 'yaml'; + +import { + ChainSubmissionStrategy, + ChainSubmissionStrategySchema, + TxSubmitterType, +} from '@hyperlane-xyz/sdk'; +import { + ProtocolType, + assert, + errorToString, + isAddress, + isPrivateKeyEvm, +} from '@hyperlane-xyz/utils'; + +import { CommandContext } from '../context/types.js'; +import { errorRed, log, logBlue, logGreen, logRed } from '../logger.js'; +import { runSingleChainSelectionStep } from '../utils/chains.js'; +import { + indentYamlOrJson, + isFile, + readYamlOrJson, + writeYamlOrJson, +} from '../utils/files.js'; +import { maskSensitiveData } from '../utils/output.js'; + +/** + * Reads and validates a chain submission strategy configuration from a file + */ +export async function readChainSubmissionStrategyConfig( + filePath: string, +): Promise { + log(`Reading submission strategy in ${filePath}`); + try { + const strategyConfig = readYamlOrJson(filePath); + + const parseResult = ChainSubmissionStrategySchema.parse(strategyConfig); + + return parseResult; + } catch (error) { + logRed(`⛔️ Error reading strategy config:`, errorToString(error)); + throw error; // Re-throw to let caller handle the error + } +} + +/** + * Safely reads chain submission strategy config, returns empty object if any errors occur + */ +export async function safeReadChainSubmissionStrategyConfig( + filePath: string, +): Promise { + try { + const trimmedFilePath = filePath.trim(); + if (!isFile(trimmedFilePath)) { + logBlue(`File ${trimmedFilePath} does not exist, returning empty config`); + return {}; + } + return await readChainSubmissionStrategyConfig(trimmedFilePath); + } catch (error) { + logRed( + `Failed to read strategy config, defaulting to empty config:`, + errorToString(error), + ); + return {}; + } +} + +export async function createStrategyConfig({ + context, + outPath, +}: { + context: CommandContext; + outPath: string; +}) { + let strategy: ChainSubmissionStrategy; + try { + const strategyObj = await readYamlOrJson(outPath); + strategy = ChainSubmissionStrategySchema.parse(strategyObj); + } catch { + strategy = writeYamlOrJson(outPath, {}, 'yaml'); + } + + const chain = await runSingleChainSelectionStep(context.chainMetadata); + const chainProtocol = context.chainMetadata[chain].protocol; + + if ( + !context.skipConfirmation && + strategy && + Object.prototype.hasOwnProperty.call(strategy, chain) + ) { + const isConfirmed = await confirm({ + message: `Default strategy for chain ${chain} already exists. Are you sure you want to overwrite existing strategy config?`, + default: false, + }); + + assert(isConfirmed, 'Strategy initialization cancelled by user.'); + } + + const isEthereum = chainProtocol === ProtocolType.Ethereum; + const submitterType = isEthereum + ? await select({ + message: 'Select the submitter type', + choices: Object.values(TxSubmitterType).map((value) => ({ + name: value, + value: value, + })), + }) + : TxSubmitterType.JSON_RPC; // Do other non-evm chains support gnosis and account impersonation? + + const submitter: Record = { type: submitterType }; + + switch (submitterType) { + case TxSubmitterType.JSON_RPC: + submitter.privateKey = await password({ + message: 'Enter the private key for JSON-RPC submission:', + validate: (pk) => (isEthereum ? isPrivateKeyEvm(pk) : true), + }); + + submitter.userAddress = isEthereum + ? await new Wallet(submitter.privateKey).getAddress() + : await input({ + message: 'Enter the user address for JSON-RPC submission:', + }); + + submitter.chain = chain; + break; + + case TxSubmitterType.IMPERSONATED_ACCOUNT: + submitter.userAddress = await input({ + message: 'Enter the user address to impersonate', + validate: (address) => + isAddress(address) ? true : 'Invalid Ethereum address', + }); + assert( + submitter.userAddress, + 'User address is required for impersonated account', + ); + break; + + case TxSubmitterType.GNOSIS_SAFE: + case TxSubmitterType.GNOSIS_TX_BUILDER: + submitter.safeAddress = await input({ + message: 'Enter the Safe address', + validate: (address) => + isAddress(address) ? true : 'Invalid Safe address', + }); + + submitter.chain = chain; + + if (submitterType === TxSubmitterType.GNOSIS_TX_BUILDER) { + submitter.version = await input({ + message: 'Enter the Safe version (default: 1.0)', + default: '1.0', + }); + } + break; + + default: + throw new Error(`Unsupported submitter type: ${submitterType}`); + } + + const strategyResult: ChainSubmissionStrategy = { + ...strategy, + [chain]: { + submitter: submitter as ChainSubmissionStrategy[string]['submitter'], + }, + }; + + try { + const strategyConfig = ChainSubmissionStrategySchema.parse(strategyResult); + logBlue(`Strategy configuration is valid. Writing to file ${outPath}:\n`); + + const maskedConfig = maskSensitiveData(strategyConfig); + log(indentYamlOrJson(yamlStringify(maskedConfig, null, 2), 4)); + + writeYamlOrJson(outPath, strategyConfig); + logGreen('✅ Successfully created a new strategy configuration.'); + } catch { + // don't log error since it may contain sensitive data + errorRed( + `The strategy configuration is invalid. Please review the submitter settings.`, + ); + } +} diff --git a/typescript/cli/src/config/warp.ts b/typescript/cli/src/config/warp.ts index 1174d0156b..f31069e091 100644 --- a/typescript/cli/src/config/warp.ts +++ b/typescript/cli/src/config/warp.ts @@ -82,7 +82,7 @@ async function fillDefaults( let owner = config.owner; if (!owner) { owner = - (await context.signer?.getAddress()) ?? + context.signerAddress ?? (await context.multiProvider.getSignerAddress(chain)); } return { @@ -122,13 +122,6 @@ export async function createWarpRouteDeployConfig({ }) { logBlue('Creating a new warp route deployment config...'); - const owner = await detectAndConfirmOrPrompt( - async () => context.signer?.getAddress(), - 'Enter the desired', - 'owner address', - 'signer', - ); - const warpChains = await runMultiChainSelectionStep({ chainMetadata: context.chainMetadata, message: 'Select chains to connect', @@ -142,6 +135,12 @@ export async function createWarpRouteDeployConfig({ let typeChoices = TYPE_CHOICES; for (const chain of warpChains) { logBlue(`${chain}: Configuring warp route...`); + const owner = await detectAndConfirmOrPrompt( + async () => context.signerAddress, + 'Enter the desired', + 'owner address', + 'signer', + ); // default to the mailbox from the registry and if not found ask to the user to submit one const chainAddresses = await context.registry.getChainAddresses(chain); diff --git a/typescript/cli/src/context/context.ts b/typescript/cli/src/context/context.ts index f9dfb34ab9..570b233cde 100644 --- a/typescript/cli/src/context/context.ts +++ b/typescript/cli/src/context/context.ts @@ -1,5 +1,5 @@ import { confirm } from '@inquirer/prompts'; -import { ethers } from 'ethers'; +import { Signer, ethers } from 'ethers'; import { DEFAULT_GITHUB_REGISTRY, @@ -16,7 +16,9 @@ import { } from '@hyperlane-xyz/sdk'; import { isHttpsUrl, isNullish, rootLogger } from '@hyperlane-xyz/utils'; +import { DEFAULT_STRATEGY_CONFIG_PATH } from '../commands/options.js'; import { isSignCommand } from '../commands/signCommands.js'; +import { safeReadChainSubmissionStrategyConfig } from '../config/strategy.js'; import { PROXY_DEPLOYED_URL } from '../consts.js'; import { forkNetworkToMultiProvider, verifyAnvil } from '../deploy/dry-run.js'; import { logBlue } from '../logger.js'; @@ -24,6 +26,8 @@ import { runSingleChainSelectionStep } from '../utils/chains.js'; import { detectAndConfirmOrPrompt } from '../utils/input.js'; import { getImpersonatedSigner, getSigner } from '../utils/keys.js'; +import { ChainResolverFactory } from './strategies/chain/ChainResolverFactory.js'; +import { MultiProtocolSignerManager } from './strategies/signer/MultiProtocolSignerManager.js'; import { CommandContext, ContextSettings, @@ -41,6 +45,7 @@ export async function contextMiddleware(argv: Record) { requiresKey, disableProxy: argv.disableProxy, skipConfirmation: argv.yes, + strategyPath: argv.strategy, }; if (!isDryRun && settings.fromAddress) throw new Error( @@ -52,6 +57,44 @@ export async function contextMiddleware(argv: Record) { argv.context = context; } +export async function signerMiddleware(argv: Record) { + const { key, requiresKey, multiProvider, strategyPath } = argv.context; + + if (!requiresKey) return argv; + + const strategyConfig = await safeReadChainSubmissionStrategyConfig( + strategyPath ?? DEFAULT_STRATEGY_CONFIG_PATH, + ); + + /** + * Intercepts Hyperlane command to determine chains. + */ + const chainStrategy = ChainResolverFactory.getStrategy(argv); + + /** + * Resolves chains based on the chain strategy. + */ + const chains = await chainStrategy.resolveChains(argv); + + /** + * Extracts signer config + */ + const multiProtocolSigner = new MultiProtocolSignerManager( + strategyConfig, + chains, + multiProvider, + { key }, + ); + + /** + * @notice Attaches signers to MultiProvider and assigns it to argv.multiProvider + */ + argv.multiProvider = await multiProtocolSigner.getMultiProvider(); + argv.multiProtocolSigner = multiProtocolSigner; + + return argv; +} + /** * Retrieves context for the user-selected command * @returns context for the current command @@ -66,19 +109,24 @@ export async function getContext({ }: ContextSettings): Promise { const registry = getRegistry(registryUri, registryOverrideUri, !disableProxy); - let signer: ethers.Wallet | undefined = undefined; - if (key || requiresKey) { + //Just for backward compatibility + let signerAddress: string | undefined = undefined; + if (key) { + let signer: Signer; ({ key, signer } = await getSigner({ key, skipConfirmation })); + signerAddress = await signer.getAddress(); } - const multiProvider = await getMultiProvider(registry, signer); + + const multiProvider = await getMultiProvider(registry); return { registry, + requiresKey, chainMetadata: multiProvider.metadata, multiProvider, key, - signer, skipConfirmation: !!skipConfirmation, + signerAddress, } as CommandContext; } diff --git a/typescript/cli/src/context/strategies/chain/ChainResolverFactory.ts b/typescript/cli/src/context/strategies/chain/ChainResolverFactory.ts new file mode 100644 index 0000000000..e417ba27b3 --- /dev/null +++ b/typescript/cli/src/context/strategies/chain/ChainResolverFactory.ts @@ -0,0 +1,36 @@ +import { CommandType } from '../../../commands/signCommands.js'; + +import { MultiChainResolver } from './MultiChainResolver.js'; +import { SingleChainResolver } from './SingleChainResolver.js'; +import { ChainResolver } from './types.js'; + +/** + * @class ChainResolverFactory + * @description Intercepts commands to determine the appropriate chain resolver strategy based on command type. + */ +export class ChainResolverFactory { + private static strategyMap: Map ChainResolver> = new Map([ + [CommandType.WARP_DEPLOY, () => MultiChainResolver.forWarpRouteConfig()], + [CommandType.WARP_SEND, () => MultiChainResolver.forOriginDestination()], + [CommandType.WARP_APPLY, () => MultiChainResolver.forWarpRouteConfig()], + [CommandType.WARP_READ, () => MultiChainResolver.forWarpCoreConfig()], + [CommandType.WARP_CHECK, () => MultiChainResolver.forWarpCoreConfig()], + [CommandType.SEND_MESSAGE, () => MultiChainResolver.forOriginDestination()], + [CommandType.AGENT_KURTOSIS, () => MultiChainResolver.forAgentKurtosis()], + [CommandType.STATUS, () => MultiChainResolver.forOriginDestination()], + [CommandType.SUBMIT, () => MultiChainResolver.forStrategyConfig()], + [CommandType.RELAYER, () => MultiChainResolver.forRelayer()], + [CommandType.CORE_APPLY, () => MultiChainResolver.forCoreApply()], + ]); + + /** + * @param argv - Command line arguments. + * @returns ChainResolver - The appropriate chain resolver strategy based on the command type. + */ + static getStrategy(argv: Record): ChainResolver { + const commandKey = `${argv._[0]}:${argv._[1] || ''}`.trim() as CommandType; + const createStrategy = + this.strategyMap.get(commandKey) || (() => new SingleChainResolver()); + return createStrategy(); + } +} diff --git a/typescript/cli/src/context/strategies/chain/MultiChainResolver.ts b/typescript/cli/src/context/strategies/chain/MultiChainResolver.ts new file mode 100644 index 0000000000..64f3257520 --- /dev/null +++ b/typescript/cli/src/context/strategies/chain/MultiChainResolver.ts @@ -0,0 +1,249 @@ +import { + ChainMap, + ChainName, + DeployedCoreAddresses, + DeployedCoreAddressesSchema, + EvmCoreModule, +} from '@hyperlane-xyz/sdk'; +import { assert } from '@hyperlane-xyz/utils'; + +import { DEFAULT_WARP_ROUTE_DEPLOYMENT_CONFIG_PATH } from '../../../commands/options.js'; +import { readCoreDeployConfigs } from '../../../config/core.js'; +import { readChainSubmissionStrategyConfig } from '../../../config/strategy.js'; +import { log } from '../../../logger.js'; +import { + extractChainsFromObj, + runMultiChainSelectionStep, + runSingleChainSelectionStep, +} from '../../../utils/chains.js'; +import { + isFile, + readYamlOrJson, + runFileSelectionStep, +} from '../../../utils/files.js'; +import { getWarpCoreConfigOrExit } from '../../../utils/warp.js'; + +import { ChainResolver } from './types.js'; + +enum ChainSelectionMode { + ORIGIN_DESTINATION, + AGENT_KURTOSIS, + WARP_CONFIG, + WARP_READ, + STRATEGY, + RELAYER, + CORE_APPLY, +} + +// This class could be broken down into multiple strategies + +/** + * @title MultiChainResolver + * @notice Resolves chains based on the specified selection mode. + */ +export class MultiChainResolver implements ChainResolver { + constructor(private mode: ChainSelectionMode) {} + + async resolveChains(argv: ChainMap): Promise { + switch (this.mode) { + case ChainSelectionMode.WARP_CONFIG: + return this.resolveWarpRouteConfigChains(argv); + case ChainSelectionMode.WARP_READ: + return this.resolveWarpCoreConfigChains(argv); + case ChainSelectionMode.AGENT_KURTOSIS: + return this.resolveAgentChains(argv); + case ChainSelectionMode.STRATEGY: + return this.resolveStrategyChains(argv); + case ChainSelectionMode.RELAYER: + return this.resolveRelayerChains(argv); + case ChainSelectionMode.CORE_APPLY: + return this.resolveCoreApplyChains(argv); + case ChainSelectionMode.ORIGIN_DESTINATION: + default: + return this.resolveOriginDestinationChains(argv); + } + } + + private async resolveWarpRouteConfigChains( + argv: Record, + ): Promise { + argv.config ||= DEFAULT_WARP_ROUTE_DEPLOYMENT_CONFIG_PATH; + argv.context.chains = await this.getWarpRouteConfigChains( + argv.config.trim(), + argv.skipConfirmation, + ); + return argv.context.chains; + } + + private async resolveWarpCoreConfigChains( + argv: Record, + ): Promise { + if (argv.symbol || argv.warp) { + const warpCoreConfig = await getWarpCoreConfigOrExit({ + context: argv.context, + warp: argv.warp, + symbol: argv.symbol, + }); + argv.context.warpCoreConfig = warpCoreConfig; + const chains = extractChainsFromObj(warpCoreConfig); + return chains; + } else if (argv.chain) { + return [argv.chain]; + } else { + throw new Error( + `Please specify either a symbol, chain and address or warp file`, + ); + } + } + + private async resolveAgentChains( + argv: Record, + ): Promise { + const { chainMetadata } = argv.context; + argv.origin = + argv.origin ?? + (await runSingleChainSelectionStep( + chainMetadata, + 'Select the origin chain', + )); + + if (!argv.targets) { + const selectedRelayChains = await runMultiChainSelectionStep({ + chainMetadata: chainMetadata, + message: 'Select chains to relay between', + requireNumber: 2, + }); + argv.targets = selectedRelayChains.join(','); + } + + return [argv.origin, ...argv.targets]; + } + + private async resolveOriginDestinationChains( + argv: Record, + ): Promise { + const { chainMetadata } = argv.context; + + argv.origin = + argv.origin ?? + (await runSingleChainSelectionStep( + chainMetadata, + 'Select the origin chain', + )); + + argv.destination = + argv.destination ?? + (await runSingleChainSelectionStep( + chainMetadata, + 'Select the destination chain', + )); + + return [argv.origin, argv.destination]; + } + + private async resolveStrategyChains( + argv: Record, + ): Promise { + const strategy = await readChainSubmissionStrategyConfig(argv.strategy); + return extractChainsFromObj(strategy); + } + + private async resolveRelayerChains( + argv: Record, + ): Promise { + return argv.chains.split(',').map((item: string) => item.trim()); + } + + private async getWarpRouteConfigChains( + configPath: string, + skipConfirmation: boolean, + ): Promise { + if (!configPath || !isFile(configPath)) { + assert(!skipConfirmation, 'Warp route deployment config is required'); + configPath = await runFileSelectionStep( + './configs', + 'Warp route deployment config', + 'warp', + ); + } else { + log(`Using warp route deployment config at ${configPath}`); + } + + // Alternative to readWarpRouteDeployConfig that doesn't use context for signer and zod validation + const warpRouteConfig = (await readYamlOrJson(configPath)) as Record< + string, + any + >; + + const chains = Object.keys(warpRouteConfig) as ChainName[]; + assert( + chains.length !== 0, + 'No chains found in warp route deployment config', + ); + + return chains; + } + + private async resolveCoreApplyChains( + argv: Record, + ): Promise { + try { + const config = readCoreDeployConfigs(argv.config); + + if (!config?.interchainAccountRouter) { + return [argv.chain]; + } + + const addresses = await argv.context.registry.getChainAddresses( + argv.chain, + ); + const coreAddresses = DeployedCoreAddressesSchema.parse( + addresses, + ) as DeployedCoreAddresses; + + const evmCoreModule = new EvmCoreModule(argv.context.multiProvider, { + chain: argv.chain, + config, + addresses: coreAddresses, + }); + + const transactions = await evmCoreModule.update(config); + + return Array.from(new Set(transactions.map((tx) => tx.chainId))).map( + (chainId) => argv.context.multiProvider.getChainName(chainId), + ); + } catch (error) { + throw new Error(`Failed to resolve core apply chains`, { + cause: error, + }); + } + } + + static forAgentKurtosis(): MultiChainResolver { + return new MultiChainResolver(ChainSelectionMode.AGENT_KURTOSIS); + } + + static forOriginDestination(): MultiChainResolver { + return new MultiChainResolver(ChainSelectionMode.ORIGIN_DESTINATION); + } + + static forRelayer(): MultiChainResolver { + return new MultiChainResolver(ChainSelectionMode.RELAYER); + } + + static forStrategyConfig(): MultiChainResolver { + return new MultiChainResolver(ChainSelectionMode.STRATEGY); + } + + static forWarpRouteConfig(): MultiChainResolver { + return new MultiChainResolver(ChainSelectionMode.WARP_CONFIG); + } + + static forWarpCoreConfig(): MultiChainResolver { + return new MultiChainResolver(ChainSelectionMode.WARP_READ); + } + + static forCoreApply(): MultiChainResolver { + return new MultiChainResolver(ChainSelectionMode.CORE_APPLY); + } +} diff --git a/typescript/cli/src/context/strategies/chain/SingleChainResolver.ts b/typescript/cli/src/context/strategies/chain/SingleChainResolver.ts new file mode 100644 index 0000000000..8dddaf3c4a --- /dev/null +++ b/typescript/cli/src/context/strategies/chain/SingleChainResolver.ts @@ -0,0 +1,25 @@ +import { ChainMap, ChainName } from '@hyperlane-xyz/sdk'; + +import { runSingleChainSelectionStep } from '../../../utils/chains.js'; + +import { ChainResolver } from './types.js'; + +/** + * @title SingleChainResolver + * @notice Strategy implementation for managing single-chain operations + * @dev Primarily used for operations like 'core:apply' and 'warp:read' + */ +export class SingleChainResolver implements ChainResolver { + /** + * @notice Determines the chain to be used for signing operations + * @dev Either uses the chain specified in argv or prompts for interactive selection + */ + async resolveChains(argv: ChainMap): Promise { + argv.chain ||= await runSingleChainSelectionStep( + argv.context.chainMetadata, + 'Select chain to connect:', + ); + + return [argv.chain]; // Explicitly return as single-item array + } +} diff --git a/typescript/cli/src/context/strategies/chain/types.ts b/typescript/cli/src/context/strategies/chain/types.ts new file mode 100644 index 0000000000..9318bed8c2 --- /dev/null +++ b/typescript/cli/src/context/strategies/chain/types.ts @@ -0,0 +1,10 @@ +import { ChainMap, ChainName } from '@hyperlane-xyz/sdk'; + +export interface ChainResolver { + /** + * Determines the chains to be used for signing + * @param argv Command arguments + * @returns Array of chain names + */ + resolveChains(argv: ChainMap): Promise; +} diff --git a/typescript/cli/src/context/strategies/signer/BaseMultiProtocolSigner.ts b/typescript/cli/src/context/strategies/signer/BaseMultiProtocolSigner.ts new file mode 100644 index 0000000000..b91242b42d --- /dev/null +++ b/typescript/cli/src/context/strategies/signer/BaseMultiProtocolSigner.ts @@ -0,0 +1,22 @@ +import { Signer } from 'ethers'; + +import { ChainName, ChainSubmissionStrategy } from '@hyperlane-xyz/sdk'; +import { Address } from '@hyperlane-xyz/utils'; + +export interface SignerConfig { + privateKey: string; + address?: Address; // For chains like StarkNet that require address + extraParams?: Record; // For any additional chain-specific params +} + +export interface IMultiProtocolSigner { + getSignerConfig(chain: ChainName): Promise | SignerConfig; + getSigner(config: SignerConfig): Signer; +} + +export abstract class BaseMultiProtocolSigner implements IMultiProtocolSigner { + constructor(protected config: ChainSubmissionStrategy) {} + + abstract getSignerConfig(chain: ChainName): Promise; + abstract getSigner(config: SignerConfig): Signer; +} diff --git a/typescript/cli/src/context/strategies/signer/MultiProtocolSignerFactory.ts b/typescript/cli/src/context/strategies/signer/MultiProtocolSignerFactory.ts new file mode 100644 index 0000000000..030f11b5f4 --- /dev/null +++ b/typescript/cli/src/context/strategies/signer/MultiProtocolSignerFactory.ts @@ -0,0 +1,79 @@ +import { password } from '@inquirer/prompts'; +import { Signer, Wallet } from 'ethers'; + +import { + ChainName, + ChainSubmissionStrategy, + ChainTechnicalStack, + MultiProvider, + TxSubmitterType, +} from '@hyperlane-xyz/sdk'; +import { ProtocolType } from '@hyperlane-xyz/utils'; + +import { + BaseMultiProtocolSigner, + IMultiProtocolSigner, + SignerConfig, +} from './BaseMultiProtocolSigner.js'; + +export class MultiProtocolSignerFactory { + static getSignerStrategy( + chain: ChainName, + strategyConfig: ChainSubmissionStrategy, + multiProvider: MultiProvider, + ): IMultiProtocolSigner { + const { protocol, technicalStack } = multiProvider.getChainMetadata(chain); + + switch (protocol) { + case ProtocolType.Ethereum: + if (technicalStack === ChainTechnicalStack.ZkSync) + return new ZKSyncSignerStrategy(strategyConfig); + return new EthereumSignerStrategy(strategyConfig); + default: + throw new Error(`Unsupported protocol: ${protocol}`); + } + } +} + +class EthereumSignerStrategy extends BaseMultiProtocolSigner { + async getSignerConfig(chain: ChainName): Promise { + const submitter = this.config[chain]?.submitter as { + type: TxSubmitterType.JSON_RPC; + privateKey?: string; + }; + + const privateKey = + submitter?.privateKey ?? + (await password({ + message: `Please enter the private key for chain ${chain}`, + })); + + return { privateKey }; + } + + getSigner(config: SignerConfig): Signer { + return new Wallet(config.privateKey); + } +} + +// 99% overlap with EthereumSignerStrategy for the sake of keeping MultiProtocolSignerFactory clean +// TODO: import ZKSync signer +class ZKSyncSignerStrategy extends BaseMultiProtocolSigner { + async getSignerConfig(chain: ChainName): Promise { + const submitter = this.config[chain]?.submitter as { + privateKey?: string; + }; + + const privateKey = + submitter?.privateKey ?? + (await password({ + message: `Please enter the private key for chain ${chain}`, + })); + + return { privateKey }; + } + + getSigner(config: SignerConfig): Signer { + return new Wallet(config.privateKey); + } +} diff --git a/typescript/cli/src/context/strategies/signer/MultiProtocolSignerManager.ts b/typescript/cli/src/context/strategies/signer/MultiProtocolSignerManager.ts new file mode 100644 index 0000000000..12f9c0f819 --- /dev/null +++ b/typescript/cli/src/context/strategies/signer/MultiProtocolSignerManager.ts @@ -0,0 +1,153 @@ +import { Signer } from 'ethers'; +import { Logger } from 'pino'; + +import { + ChainName, + ChainSubmissionStrategy, + MultiProvider, +} from '@hyperlane-xyz/sdk'; +import { assert, rootLogger } from '@hyperlane-xyz/utils'; + +import { ENV } from '../../../utils/env.js'; + +import { IMultiProtocolSigner } from './BaseMultiProtocolSigner.js'; +import { MultiProtocolSignerFactory } from './MultiProtocolSignerFactory.js'; + +export interface MultiProtocolSignerOptions { + logger?: Logger; + key?: string; +} + +/** + * @title MultiProtocolSignerManager + * @dev Context manager for signers across multiple protocols + */ +export class MultiProtocolSignerManager { + protected readonly signerStrategies: Map; + protected readonly signers: Map; + public readonly logger: Logger; + + constructor( + protected readonly submissionStrategy: ChainSubmissionStrategy, + protected readonly chains: ChainName[], + protected readonly multiProvider: MultiProvider, + protected readonly options: MultiProtocolSignerOptions = {}, + ) { + this.logger = + options?.logger || + rootLogger.child({ + module: 'MultiProtocolSignerManager', + }); + this.signerStrategies = new Map(); + this.signers = new Map(); + this.initializeStrategies(); + } + + /** + * @notice Sets up chain-specific signer strategies + */ + protected initializeStrategies(): void { + for (const chain of this.chains) { + const strategy = MultiProtocolSignerFactory.getSignerStrategy( + chain, + this.submissionStrategy, + this.multiProvider, + ); + this.signerStrategies.set(chain, strategy); + } + } + + /** + * @dev Configures signers for EVM chains in MultiProvider + */ + async getMultiProvider(): Promise { + for (const chain of this.chains) { + const signer = await this.initSigner(chain); + this.multiProvider.setSigner(chain, signer); + } + + return this.multiProvider; + } + + /** + * @notice Creates signer for specific chain + */ + async initSigner(chain: ChainName): Promise { + const { privateKey } = await this.resolveConfig(chain); + + const signerStrategy = this.signerStrategies.get(chain); + assert(signerStrategy, `No signer strategy found for chain ${chain}`); + + return signerStrategy.getSigner({ privateKey }); + } + + /** + * @notice Creates signers for all chains + */ + async initAllSigners(): Promise { + const signerConfigs = await this.resolveAllConfigs(); + + for (const { chain, privateKey } of signerConfigs) { + const signerStrategy = this.signerStrategies.get(chain); + if (signerStrategy) { + this.signers.set(chain, signerStrategy.getSigner({ privateKey })); + } + } + + return this.signers; + } + + /** + * @notice Resolves all chain configurations + */ + private async resolveAllConfigs(): Promise< + Array<{ chain: ChainName; privateKey: string }> + > { + return Promise.all(this.chains.map((chain) => this.resolveConfig(chain))); + } + + /** + * @notice Resolves single chain configuration + */ + private async resolveConfig( + chain: ChainName, + ): Promise<{ chain: ChainName; privateKey: string }> { + const signerStrategy = this.signerStrategies.get(chain); + assert(signerStrategy, `No signer strategy found for chain ${chain}`); + + let privateKey: string; + + if (this.options.key) { + this.logger.info( + `Using private key passed via CLI --key flag for chain ${chain}`, + ); + privateKey = this.options.key; + } else if (ENV.HYP_KEY) { + this.logger.info(`Using private key from .env for chain ${chain}`); + privateKey = ENV.HYP_KEY; + } else { + privateKey = await this.extractPrivateKey(chain, signerStrategy); + } + + return { chain, privateKey }; + } + + /** + * @notice Gets private key from strategy + */ + private async extractPrivateKey( + chain: ChainName, + signerStrategy: IMultiProtocolSigner, + ): Promise { + const strategyConfig = await signerStrategy.getSignerConfig(chain); + assert( + strategyConfig.privateKey, + `No private key found for chain ${chain}`, + ); + + this.logger.info( + `Extracting private key from strategy config/user prompt for chain ${chain}`, + ); + return strategyConfig.privateKey; + } +} diff --git a/typescript/cli/src/context/types.ts b/typescript/cli/src/context/types.ts index 6c3a17c5ff..c320ff3cac 100644 --- a/typescript/cli/src/context/types.ts +++ b/typescript/cli/src/context/types.ts @@ -6,6 +6,7 @@ import type { ChainMap, ChainMetadata, MultiProvider, + WarpCoreConfig, } from '@hyperlane-xyz/sdk'; export interface ContextSettings { @@ -16,6 +17,7 @@ export interface ContextSettings { requiresKey?: boolean; disableProxy?: boolean; skipConfirmation?: boolean; + strategyPath?: string; } export interface CommandContext { @@ -24,7 +26,10 @@ export interface CommandContext { multiProvider: MultiProvider; skipConfirmation: boolean; key?: string; - signer?: ethers.Signer; + // just for evm chains backward compatibility + signerAddress?: string; + warpCoreConfig?: WarpCoreConfig; + strategyPath?: string; } export interface WriteCommandContext extends CommandContext { diff --git a/typescript/cli/src/deploy/agent.ts b/typescript/cli/src/deploy/agent.ts index ca490fc5fb..a36955a3f3 100644 --- a/typescript/cli/src/deploy/agent.ts +++ b/typescript/cli/src/deploy/agent.ts @@ -21,6 +21,7 @@ export async function runKurtosisAgentDeploy({ relayChains?: string; agentConfigurationPath?: string; }) { + // Future works: decide what to do with this, since its handled in MultiChainResolver - AGENT_KURTOSIS mode if (!originChain) { originChain = await runSingleChainSelectionStep( context.chainMetadata, diff --git a/typescript/cli/src/deploy/core.ts b/typescript/cli/src/deploy/core.ts index 06d08b13cd..7ce8a0247c 100644 --- a/typescript/cli/src/deploy/core.ts +++ b/typescript/cli/src/deploy/core.ts @@ -43,7 +43,6 @@ export async function runCoreDeploy(params: DeployParams) { let chain = params.chain; const { - signer, isDryRun, chainMetadata, dryRunChain, @@ -62,13 +61,14 @@ export async function runCoreDeploy(params: DeployParams) { 'Select chain to connect:', ); } - let apiKeys: ChainMap = {}; if (!skipConfirmation) apiKeys = await requestAndSaveApiKeys([chain], chainMetadata, registry); + const signer = multiProvider.getSigner(chain); + const deploymentParams: DeployParams = { - context, + context: { ...context, signer }, chain, config, }; diff --git a/typescript/cli/src/deploy/utils.ts b/typescript/cli/src/deploy/utils.ts index 125e7b1e77..f5ac01a175 100644 --- a/typescript/cli/src/deploy/utils.ts +++ b/typescript/cli/src/deploy/utils.ts @@ -41,7 +41,7 @@ export async function runPreflightChecksForChains({ chainsToGasCheck?: ChainName[]; }) { log('Running pre-flight checks for chains...'); - const { signer, multiProvider } = context; + const { multiProvider } = context; if (!chains?.length) throw new Error('Empty chain selection'); for (const chain of chains) { @@ -49,15 +49,14 @@ export async function runPreflightChecksForChains({ if (!metadata) throw new Error(`No chain config found for ${chain}`); if (metadata.protocol !== ProtocolType.Ethereum) throw new Error('Only Ethereum chains are supported for now'); + const signer = multiProvider.getSigner(chain); + assertSigner(signer); + logGreen(`✅ ${chain} signer is valid`); } logGreen('✅ Chains are valid'); - assertSigner(signer); - logGreen('✅ Signer is valid'); - await nativeBalancesAreSufficient( multiProvider, - signer, chainsToGasCheck ?? chains, minGas, ); @@ -70,8 +69,13 @@ export async function runDeployPlanStep({ context: WriteCommandContext; chain: ChainName; }) { - const { signer, chainMetadata: chainMetadataMap, skipConfirmation } = context; - const address = await signer.getAddress(); + const { + chainMetadata: chainMetadataMap, + multiProvider, + skipConfirmation, + } = context; + + const address = await multiProvider.getSigner(chain).getAddress(); logBlue('\nDeployment plan'); logGray('==============='); @@ -124,7 +128,7 @@ export function isZODISMConfig(filepath: string): boolean { export async function prepareDeploy( context: WriteCommandContext, - userAddress: Address, + userAddress: Address | null, chains: ChainName[], ): Promise> { const { multiProvider, isDryRun } = context; @@ -134,7 +138,9 @@ export async function prepareDeploy( const provider = isDryRun ? getLocalProvider(ENV.ANVIL_IP_ADDR, ENV.ANVIL_PORT) : multiProvider.getProvider(chain); - const currentBalance = await provider.getBalance(userAddress); + const address = + userAddress ?? (await multiProvider.getSigner(chain).getAddress()); + const currentBalance = await provider.getBalance(address); initialBalances[chain] = currentBalance; }), ); @@ -145,7 +151,7 @@ export async function completeDeploy( context: WriteCommandContext, command: string, initialBalances: Record, - userAddress: Address, + userAddress: Address | null, chains: ChainName[], ) { const { multiProvider, isDryRun } = context; @@ -154,7 +160,9 @@ export async function completeDeploy( const provider = isDryRun ? getLocalProvider(ENV.ANVIL_IP_ADDR, ENV.ANVIL_PORT) : multiProvider.getProvider(chain); - const currentBalance = await provider.getBalance(userAddress); + const address = + userAddress ?? (await multiProvider.getSigner(chain).getAddress()); + const currentBalance = await provider.getBalance(address); const balanceDelta = initialBalances[chain].sub(currentBalance); if (isDryRun && balanceDelta.lt(0)) break; logPink( diff --git a/typescript/cli/src/deploy/warp.ts b/typescript/cli/src/deploy/warp.ts index 018244700a..e94bd709da 100644 --- a/typescript/cli/src/deploy/warp.ts +++ b/typescript/cli/src/deploy/warp.ts @@ -102,7 +102,7 @@ export async function runWarpRouteDeploy({ context: WriteCommandContext; warpRouteDeploymentConfigPath?: string; }) { - const { signer, skipConfirmation, chainMetadata, registry } = context; + const { skipConfirmation, chainMetadata, registry } = context; if ( !warpRouteDeploymentConfigPath || @@ -149,13 +149,8 @@ export async function runWarpRouteDeploy({ minGas: MINIMUM_WARP_DEPLOY_GAS, }); - const userAddress = await signer.getAddress(); + const initialBalances = await prepareDeploy(context, null, ethereumChains); - const initialBalances = await prepareDeploy( - context, - userAddress, - ethereumChains, - ); const deployedContracts = await executeDeploy(deploymentParams, apiKeys); const warpCoreConfig = await getWarpCoreConfig( @@ -165,13 +160,7 @@ export async function runWarpRouteDeploy({ await writeDeploymentArtifacts(warpCoreConfig, context); - await completeDeploy( - context, - 'warp', - initialBalances, - userAddress, - ethereumChains, - ); + await completeDeploy(context, 'warp', initialBalances, null, ethereumChains!); } async function runDeployPlanStep({ context, warpDeployConfig }: DeployParams) { diff --git a/typescript/cli/src/read/warp.ts b/typescript/cli/src/read/warp.ts index bd5d01e95e..169593c5e9 100644 --- a/typescript/cli/src/read/warp.ts +++ b/typescript/cli/src/read/warp.ts @@ -34,11 +34,13 @@ export async function runWarpRouteRead({ let addresses: ChainMap; if (symbol || warp) { - const warpCoreConfig = await getWarpCoreConfigOrExit({ - context, - warp, - symbol, - }); + const warpCoreConfig = + context.warpCoreConfig ?? // this case is be handled by MultiChainHandler.forWarpCoreConfig() interceptor + (await getWarpCoreConfigOrExit({ + context, + warp, + symbol, + })); // TODO: merge with XERC20TokenAdapter and WarpRouteReader const xerc20Limits = await Promise.all( diff --git a/typescript/cli/src/send/transfer.ts b/typescript/cli/src/send/transfer.ts index a89eb6aa99..2929b09c6e 100644 --- a/typescript/cli/src/send/transfer.ts +++ b/typescript/cli/src/send/transfer.ts @@ -40,8 +40,8 @@ export async function sendTestTransfer({ }: { context: WriteCommandContext; warpCoreConfig: WarpCoreConfig; - origin?: ChainName; - destination?: ChainName; + origin?: ChainName; // resolved in signerMiddleware + destination?: ChainName; // resolved in signerMiddleware amount: string; recipient?: string; timeoutSec: number; @@ -106,10 +106,15 @@ async function executeDelivery({ skipWaitForDelivery: boolean; selfRelay?: boolean; }) { - const { signer, multiProvider, registry } = context; + const { multiProvider, registry } = context; + const signer = multiProvider.getSigner(origin); + const recipientSigner = multiProvider.getSigner(destination); + + const recipientAddress = await recipientSigner.getAddress(); const signerAddress = await signer.getAddress(); - recipient ||= signerAddress; + + recipient ||= recipientAddress; const chainAddresses = await registry.getAddresses(); @@ -136,12 +141,11 @@ async function executeDelivery({ token = warpCore.findToken(origin, routerAddress)!; } - const senderAddress = await signer.getAddress(); const errors = await warpCore.validateTransfer({ originTokenAmount: token.amount(amount), destination, - recipient: recipient ?? senderAddress, - sender: senderAddress, + recipient, + sender: signerAddress, }); if (errors) { logRed('Error validating transfer', JSON.stringify(errors)); @@ -152,8 +156,8 @@ async function executeDelivery({ const transferTxs = await warpCore.getTransferRemoteTxs({ originTokenAmount: new TokenAmount(amount, token), destination, - sender: senderAddress, - recipient: recipient ?? senderAddress, + sender: signerAddress, + recipient, }); const txReceipts = []; @@ -172,7 +176,7 @@ async function executeDelivery({ const parsed = parseWarpRouteMessage(message.parsed.body); logBlue( - `Sent transfer from sender (${senderAddress}) on ${origin} to recipient (${recipient}) on ${destination}.`, + `Sent transfer from sender (${signerAddress}) on ${origin} to recipient (${recipient}) on ${destination}.`, ); logBlue(`Message ID: ${message.id}`); log(`Message:\n${indentYamlOrJson(yamlStringify(message, null, 2), 4)}`); diff --git a/typescript/cli/src/tests/commands/helpers.ts b/typescript/cli/src/tests/commands/helpers.ts index c6bfdc9a7c..7853815459 100644 --- a/typescript/cli/src/tests/commands/helpers.ts +++ b/typescript/cli/src/tests/commands/helpers.ts @@ -1,3 +1,4 @@ +import { ethers } from 'ethers'; import { $ } from 'zx'; import { ERC20Test__factory, ERC4626Test__factory } from '@hyperlane-xyz/core'; @@ -160,6 +161,9 @@ export async function deployToken(privateKey: string, chain: string) { key: privateKey, }); + // Future works: make signer compatible with protocol/chain stack + multiProvider.setSigner(chain, new ethers.Wallet(privateKey)); + const token = await new ERC20Test__factory( multiProvider.getSigner(chain), ).deploy('token', 'token', '100000000000000000000', 18); @@ -179,6 +183,9 @@ export async function deploy4626Vault( key: privateKey, }); + // Future works: make signer compatible with protocol/chain stack + multiProvider.setSigner(chain, new ethers.Wallet(privateKey)); + const vault = await new ERC4626Test__factory( multiProvider.getSigner(chain), ).deploy(tokenAddress, 'VAULT', 'VAULT'); diff --git a/typescript/cli/src/utils/balances.ts b/typescript/cli/src/utils/balances.ts index 4536353e57..2a6e6fcb8a 100644 --- a/typescript/cli/src/utils/balances.ts +++ b/typescript/cli/src/utils/balances.ts @@ -8,12 +8,9 @@ import { logGray, logGreen, logRed } from '../logger.js'; export async function nativeBalancesAreSufficient( multiProvider: MultiProvider, - signer: ethers.Signer, chains: ChainName[], minGas: string, ) { - const address = await signer.getAddress(); - const sufficientBalances: boolean[] = []; for (const chain of chains) { // Only Ethereum chains are supported @@ -21,7 +18,7 @@ export async function nativeBalancesAreSufficient( logGray(`Skipping balance check for non-EVM chain: ${chain}`); continue; } - + const address = multiProvider.getSigner(chain).getAddress(); const provider = multiProvider.getProvider(chain); const gasPrice = await provider.getGasPrice(); const minBalanceWei = gasPrice.mul(minGas).toString(); diff --git a/typescript/cli/src/utils/chains.ts b/typescript/cli/src/utils/chains.ts index add11203d0..7e2eaccd0a 100644 --- a/typescript/cli/src/utils/chains.ts +++ b/typescript/cli/src/utils/chains.ts @@ -171,3 +171,36 @@ function handleNewChain(chainNames: string[]) { process.exit(0); } } + +/** + * @notice Extracts chain names from a nested configuration object + * @param config Object to search for chain names + * @return Array of discovered chain names + */ +export function extractChainsFromObj(config: Record): string[] { + const chains: string[] = []; + + // Recursively search for chain/chainName fields + function findChainFields(obj: any) { + if (obj === null || typeof obj !== 'object') return; + + if (Array.isArray(obj)) { + obj.forEach((item) => findChainFields(item)); + return; + } + + if ('chain' in obj) { + chains.push(obj.chain); + } + + if ('chainName' in obj) { + chains.push(obj.chainName); + } + + // Recursively search in all nested values + Object.values(obj).forEach((value) => findChainFields(value)); + } + + findChainFields(config); + return chains; +} diff --git a/typescript/cli/src/utils/output.ts b/typescript/cli/src/utils/output.ts index 442b8a0906..2e1acfdf41 100644 --- a/typescript/cli/src/utils/output.ts +++ b/typescript/cli/src/utils/output.ts @@ -54,3 +54,50 @@ export function formatYamlViolationsOutput( return highlightedLines.join('\n'); } + +/** + * @notice Masks sensitive key with dots + * @param key Sensitive key to mask + * @return Masked key + */ +export function maskSensitiveKey(key: string): string { + if (!key) return key; + const middle = '•'.repeat(key.length); + return `${middle}`; +} + +const SENSITIVE_PATTERNS = [ + 'privatekey', + 'key', + 'secret', + 'secretkey', + 'password', +]; + +const isSensitiveKey = (key: string) => { + const lowerKey = key.toLowerCase(); + return SENSITIVE_PATTERNS.some((pattern) => lowerKey.includes(pattern)); +}; + +/** + * @notice Recursively masks sensitive data in objects + * @param obj Object with potential sensitive data + * @return Object with masked sensitive data + */ +export function maskSensitiveData(obj: any): any { + if (!obj) return obj; + + if (typeof obj === 'object') { + const masked = { ...obj }; + for (const [key, value] of Object.entries(masked)) { + if (isSensitiveKey(key) && typeof value === 'string') { + masked[key] = maskSensitiveKey(value); + } else if (typeof value === 'object') { + masked[key] = maskSensitiveData(value); + } + } + return masked; + } + + return obj; +} diff --git a/typescript/sdk/src/providers/transactions/submitter/ethersV5/types.ts b/typescript/sdk/src/providers/transactions/submitter/ethersV5/types.ts index d1e1c7a90c..bf0d29d540 100644 --- a/typescript/sdk/src/providers/transactions/submitter/ethersV5/types.ts +++ b/typescript/sdk/src/providers/transactions/submitter/ethersV5/types.ts @@ -23,6 +23,8 @@ export type EV5GnosisSafeTxBuilderProps = z.infer< export const EV5JsonRpcTxSubmitterPropsSchema = z.object({ chain: ZChainName, + userAddress: ZHash.optional(), + privateKey: ZHash.optional(), }); export type EV5JsonRpcTxSubmitterProps = z.infer< diff --git a/typescript/utils/src/addresses.ts b/typescript/utils/src/addresses.ts index 29a35b6b88..a244c810ba 100644 --- a/typescript/utils/src/addresses.ts +++ b/typescript/utils/src/addresses.ts @@ -1,6 +1,6 @@ import { fromBech32, normalizeBech32, toBech32 } from '@cosmjs/encoding'; import { PublicKey } from '@solana/web3.js'; -import { utils as ethersUtils } from 'ethers'; +import { Wallet, utils as ethersUtils } from 'ethers'; import { isNullish } from './typeof.js'; import { Address, HexString, ProtocolType } from './types.js'; @@ -380,3 +380,11 @@ export function ensure0x(hexstr: string) { export function strip0x(hexstr: string) { return hexstr.startsWith('0x') ? hexstr.slice(2) : hexstr; } + +export function isPrivateKeyEvm(privateKey: string): boolean { + try { + return new Wallet(privateKey).privateKey === privateKey; + } catch { + throw new Error('Provided Private Key is not EVM compatible!'); + } +} diff --git a/typescript/utils/src/index.ts b/typescript/utils/src/index.ts index 8314418631..f4bd9779cb 100644 --- a/typescript/utils/src/index.ts +++ b/typescript/utils/src/index.ts @@ -26,6 +26,7 @@ export { isValidAddressCosmos, isValidAddressEvm, isValidAddressSealevel, + isPrivateKeyEvm, isValidTransactionHash, isValidTransactionHashCosmos, isValidTransactionHashEvm, From 5b705276183b4c0665ee8be52bd63e8b0173de5c Mon Sep 17 00:00:00 2001 From: Trevor Porter Date: Tue, 10 Dec 2024 15:41:12 +0000 Subject: [PATCH 03/18] chore: move viction warp routes to default ISMs (#4983) ### Description - after the recent viction validator set change, just move this over to the default ISM so we always get the latest and greatest - fixes our warp checker violations - kinda stylistic, but just explicitly specifying the zero address as the ISM address on the viction side too ### Drive-by changes ### Related issues ### Backward compatibility ### Testing --- .../getEthereumVictionETHWarpConfig.ts | 13 ++++--------- .../getEthereumVictionUSDCWarpConfig.ts | 14 ++++---------- .../getEthereumVictionUSDTWarpConfig.ts | 13 ++++--------- 3 files changed, 12 insertions(+), 28 deletions(-) diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumVictionETHWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumVictionETHWarpConfig.ts index 4617aec50e..f058edb64b 100644 --- a/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumVictionETHWarpConfig.ts +++ b/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumVictionETHWarpConfig.ts @@ -1,10 +1,10 @@ +import { ethers } from 'ethers'; + import { ChainMap, HypTokenRouterConfig, OwnableConfig, TokenType, - buildAggregationIsmConfigs, - defaultMultisigConfigs, } from '@hyperlane-xyz/sdk'; import { RouterConfigWithoutOwner } from '../../../../../src/config/warp.js'; @@ -13,12 +13,6 @@ export const getEthereumVictionETHWarpConfig = async ( routerConfig: ChainMap, abacusWorksEnvOwnerConfig: ChainMap, ): Promise> => { - const ismConfig = buildAggregationIsmConfigs( - 'ethereum', - ['viction'], - defaultMultisigConfigs, - ).viction; - const viction: HypTokenRouterConfig = { ...routerConfig.viction, ...abacusWorksEnvOwnerConfig.viction, @@ -28,6 +22,7 @@ export const getEthereumVictionETHWarpConfig = async ( decimals: 18, totalSupply: 0, gas: 50_000, + interchainSecurityModule: ethers.constants.AddressZero, }; const ethereum: HypTokenRouterConfig = { @@ -35,7 +30,7 @@ export const getEthereumVictionETHWarpConfig = async ( ...abacusWorksEnvOwnerConfig.ethereum, type: TokenType.native, gas: 65_000, - interchainSecurityModule: ismConfig, + interchainSecurityModule: ethers.constants.AddressZero, }; return { diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumVictionUSDCWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumVictionUSDCWarpConfig.ts index 8ea4026b82..7bcf283573 100644 --- a/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumVictionUSDCWarpConfig.ts +++ b/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumVictionUSDCWarpConfig.ts @@ -1,10 +1,10 @@ +import { ethers } from 'ethers'; + import { ChainMap, HypTokenRouterConfig, OwnableConfig, TokenType, - buildAggregationIsmConfigs, - defaultMultisigConfigs, } from '@hyperlane-xyz/sdk'; import { @@ -16,13 +16,6 @@ export const getEthereumVictionUSDCWarpConfig = async ( routerConfig: ChainMap, abacusWorksEnvOwnerConfig: ChainMap, ): Promise> => { - // commit that the config was copied from https://github.com/hyperlane-xyz/hyperlane-monorepo/pull/3067/commits/7ed5b460034ea5e140c6ff86bcd6baf6ebb824c4#diff-fab5dd1a27c76e4310699c57ccf92ab6274ef0acf17e079b17270cedf4057775R109 - const ismConfig = buildAggregationIsmConfigs( - 'ethereum', - ['viction'], - defaultMultisigConfigs, - ).viction; - const viction: HypTokenRouterConfig = { ...routerConfig.viction, ...abacusWorksEnvOwnerConfig.viction, @@ -32,6 +25,7 @@ export const getEthereumVictionUSDCWarpConfig = async ( decimals: 6, totalSupply: 0, gas: 75_000, + interchainSecurityModule: ethers.constants.AddressZero, }; const ethereum: HypTokenRouterConfig = { @@ -40,7 +34,7 @@ export const getEthereumVictionUSDCWarpConfig = async ( type: TokenType.collateral, token: tokens.ethereum.USDC, gas: 65_000, - interchainSecurityModule: ismConfig, + interchainSecurityModule: ethers.constants.AddressZero, }; return { diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumVictionUSDTWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumVictionUSDTWarpConfig.ts index 3b98d5debc..3001880aa3 100644 --- a/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumVictionUSDTWarpConfig.ts +++ b/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumVictionUSDTWarpConfig.ts @@ -1,10 +1,10 @@ +import { ethers } from 'ethers'; + import { ChainMap, HypTokenRouterConfig, OwnableConfig, TokenType, - buildAggregationIsmConfigs, - defaultMultisigConfigs, } from '@hyperlane-xyz/sdk'; import { @@ -16,12 +16,6 @@ export const getEthereumVictionUSDTWarpConfig = async ( routerConfig: ChainMap, abacusWorksEnvOwnerConfig: ChainMap, ): Promise> => { - const ismConfig = buildAggregationIsmConfigs( - 'ethereum', - ['viction'], - defaultMultisigConfigs, - ).viction; - const viction: HypTokenRouterConfig = { ...routerConfig.viction, ...abacusWorksEnvOwnerConfig.viction, @@ -31,6 +25,7 @@ export const getEthereumVictionUSDTWarpConfig = async ( decimals: 6, totalSupply: 0, gas: 75_000, + interchainSecurityModule: ethers.constants.AddressZero, }; const ethereum: HypTokenRouterConfig = { @@ -39,7 +34,7 @@ export const getEthereumVictionUSDTWarpConfig = async ( type: TokenType.collateral, token: tokens.ethereum.USDT, gas: 65_000, - interchainSecurityModule: ismConfig, + interchainSecurityModule: ethers.constants.AddressZero, }; return { From 5942e9cff4dfe6a829525012ee0ad556340baeae Mon Sep 17 00:00:00 2001 From: Paul Balaji <10051819+paulbalaji@users.noreply.github.com> Date: Wed, 11 Dec 2024 10:43:14 +0000 Subject: [PATCH 04/18] feat: enroll appchain, treasure, zklink (#4973) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Description - enroll appchain, treasure, zklink - owner updates - add swell safe - add ICAs for appchain, lumiaprism - validator updates - swell - add luganodes, merkly, mitosis validator - lumiaprism - add merkly, mitosis validator - Lisk - lisk’s own one - Aleph Zero - alephzero’s own one - vana - add p2p - treasure - add treasure, mitosis, P2P validator - appchain - add merkly, mitosis validator - zklink - add merkly, mitosis validator - introduce `HyperlaneICAChecker` to only check/enroll Ethereum ICA router - add ability to read `ProxyAdmin` owner transfer txs ### Drive-by changes - igp updates - fix: do owner check when inferring calls in governor ### Related issues ### Backward compatibility ### Testing --------- Signed-off-by: pbio <10051819+paulbalaji@users.noreply.github.com> --- .changeset/smooth-rocks-hammer.md | 5 + .../config/environments/mainnet3/chains.ts | 10 + .../environments/mainnet3/gasPrices.json | 32 +-- .../config/environments/mainnet3/owners.ts | 7 + .../environments/mainnet3/tokenPrices.json | 188 +++++++++--------- typescript/infra/scripts/check/check-utils.ts | 3 +- typescript/infra/scripts/safes/parse-txs.ts | 9 +- .../infra/src/govern/HyperlaneAppGovernor.ts | 14 ++ .../infra/src/govern/HyperlaneHaasGovernor.ts | 4 +- .../infra/src/govern/HyperlaneICAChecker.ts | 63 ++++++ .../infra/src/tx/govern-transaction-reader.ts | 55 ++++- typescript/sdk/src/consts/multisigIsm.ts | 47 ++++- 12 files changed, 315 insertions(+), 122 deletions(-) create mode 100644 .changeset/smooth-rocks-hammer.md create mode 100644 typescript/infra/src/govern/HyperlaneICAChecker.ts diff --git a/.changeset/smooth-rocks-hammer.md b/.changeset/smooth-rocks-hammer.md new file mode 100644 index 0000000000..8cc170aac9 --- /dev/null +++ b/.changeset/smooth-rocks-hammer.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/sdk': patch +--- + +Update default validator sets for alephzeroevmmainnet, appchain, lisk, lumiaprism, swell, treasure, vana, zklink. diff --git a/typescript/infra/config/environments/mainnet3/chains.ts b/typescript/infra/config/environments/mainnet3/chains.ts index 66d12a8937..f0d7f66400 100644 --- a/typescript/infra/config/environments/mainnet3/chains.ts +++ b/typescript/infra/config/environments/mainnet3/chains.ts @@ -65,6 +65,16 @@ export const chainMetadataOverrides: ChainMap> = { // maxFeePerGas: 100000 * 10 ** 9, // 100,000 gwei // }, // }, + // taiko: { + // transactionOverrides: { + // gasPrice: 25 * 10 ** 7, // 0.25 gwei + // }, + // }, + // linea: { + // transactionOverrides: { + // gasPrice: 5 * 10 ** 8, // 0.5 gwei + // }, + // }, // zircuit: { // blocks: { // confirmations: 3, diff --git a/typescript/infra/config/environments/mainnet3/gasPrices.json b/typescript/infra/config/environments/mainnet3/gasPrices.json index 1009f5eee1..1c5f05b28a 100644 --- a/typescript/infra/config/environments/mainnet3/gasPrices.json +++ b/typescript/infra/config/environments/mainnet3/gasPrices.json @@ -28,7 +28,7 @@ "decimals": 9 }, "astarzkevm": { - "amount": "0.24", + "amount": "0.0988", "decimals": 9 }, "flame": { @@ -36,11 +36,11 @@ "decimals": 9 }, "avalanche": { - "amount": "25.0", + "amount": "27.735398516", "decimals": 9 }, "b3": { - "amount": "0.001000252", + "amount": "0.001000253", "decimals": 9 }, "base": { @@ -60,7 +60,7 @@ "decimals": 9 }, "boba": { - "amount": "0.001000047", + "amount": "0.001000059", "decimals": 9 }, "bsc": { @@ -140,7 +140,7 @@ "decimals": 9 }, "gnosis": { - "amount": "1.500000008", + "amount": "1.500000007", "decimals": 9 }, "gravity": { @@ -176,11 +176,11 @@ "decimals": 9 }, "lisk": { - "amount": "0.00100103", + "amount": "0.001001147", "decimals": 9 }, "lukso": { - "amount": "0.921815267", + "amount": "1.109955713", "decimals": 9 }, "lumia": { @@ -192,7 +192,7 @@ "decimals": 9 }, "mantapacific": { - "amount": "0.00300029", + "amount": "0.003000983", "decimals": 9 }, "mantle": { @@ -216,7 +216,7 @@ "decimals": 9 }, "mode": { - "amount": "0.001000252", + "amount": "0.001001363", "decimals": 9 }, "molten": { @@ -240,7 +240,7 @@ "decimals": 9 }, "optimism": { - "amount": "0.001065045", + "amount": "0.001000469", "decimals": 9 }, "orderly": { @@ -264,7 +264,7 @@ "decimals": 9 }, "prom": { - "amount": "546.0", + "amount": "51.9", "decimals": 9 }, "proofofplay": { @@ -276,7 +276,7 @@ "decimals": 9 }, "real": { - "amount": "0.04", + "amount": "0.022", "decimals": 9 }, "redstone": { @@ -296,7 +296,7 @@ "decimals": 9 }, "sei": { - "amount": "100.0", + "amount": "3.328028877", "decimals": 9 }, "shibarium": { @@ -336,7 +336,7 @@ "decimals": 9 }, "treasure": { - "amount": "10000.0", + "amount": "702.999550885", "decimals": 9 }, "unichain": { @@ -344,7 +344,7 @@ "decimals": 9 }, "vana": { - "amount": "0.002488334", + "amount": "0.002986", "decimals": 9 }, "viction": { @@ -352,7 +352,7 @@ "decimals": 9 }, "worldchain": { - "amount": "0.00100026", + "amount": "0.001000255", "decimals": 9 }, "xai": { diff --git a/typescript/infra/config/environments/mainnet3/owners.ts b/typescript/infra/config/environments/mainnet3/owners.ts index f37ac77af8..5b495d316f 100644 --- a/typescript/infra/config/environments/mainnet3/owners.ts +++ b/typescript/infra/config/environments/mainnet3/owners.ts @@ -56,6 +56,7 @@ export const safes: ChainMap
= { endurance: '0xaCD1865B262C89Fb0b50dcc8fB095330ae8F35b5', zircuit: '0x9e2fe7723b018d02cDE4f5cC1A9bC9C65b922Fc8', zeronetwork: '0xCB21F61A3c8139F18e635d45aD1e62A4A61d2c3D', + swell: '0x5F7771EA40546e2932754C263455Cb0023a55ca7', }; export const icaOwnerChain = 'ethereum'; @@ -166,6 +167,12 @@ export const icas: Partial< vana: '0x29dfa34765e29ea353FC8aB70A19e32a5578E603', bsquared: '0xd9564EaaA68A327933f758A54450D3A0531E60BB', superseed: '0x29dfa34765e29ea353FC8aB70A19e32a5578E603', + + // Dec 4, 2024 batch + // ---------------------------------------------------------- + // swell: '0xff8326468e7AaB51c53D3569cf7C45Dd54c11687', // already has a safe + lumiaprism: '0xAFfA863646D1bC74ecEC0dB1070f069Af065EBf5', + appchain: '0x4F25DFFd10A6D61C365E1a605d07B2ab0E82A7E6', } as const; export const DEPLOYER = '0xa7ECcdb9Be08178f896c26b7BbD8C3D4E844d9Ba'; diff --git a/typescript/infra/config/environments/mainnet3/tokenPrices.json b/typescript/infra/config/environments/mainnet3/tokenPrices.json index e340241632..9567a31775 100644 --- a/typescript/infra/config/environments/mainnet3/tokenPrices.json +++ b/typescript/infra/config/environments/mainnet3/tokenPrices.json @@ -1,99 +1,99 @@ { - "ancient8": "3628.85", - "alephzeroevmmainnet": "0.61841", - "apechain": "1.52", - "appchain": "3628.85", - "arbitrum": "3628.85", - "arbitrumnova": "3628.85", - "astar": "0.078025", - "astarzkevm": "3628.85", - "flame": "7.36", - "avalanche": "48.31", - "b3": "3628.85", - "base": "3628.85", - "bitlayer": "95794", - "blast": "3628.85", - "bob": "3628.85", - "boba": "3628.85", - "bsc": "639.49", - "bsquared": "95794", - "celo": "0.983257", - "cheesechain": "0.00180324", - "chilizmainnet": "0.104233", - "coredao": "1.43", - "cyber": "3628.85", - "degenchain": "0.01711559", - "dogechain": "0.421781", - "duckchain": "6.5", - "eclipsemainnet": "3628.85", - "endurance": "3.09", - "ethereum": "3628.85", - "everclear": "3628.85", - "fantom": "1.034", - "flare": "0.03388989", - "flowmainnet": "1.01", - "fraxtal": "3614.4", - "fusemainnet": "0.03486937", - "gnosis": "0.997956", - "gravity": "0.03730451", - "harmony": "0.02834153", - "immutablezkevmmainnet": "1.94", - "inevm": "30.08", - "injective": "30.08", - "kaia": "0.357047", - "kroma": "3628.85", - "linea": "3628.85", - "lisk": "3628.85", - "lukso": "3.33", - "lumia": "1.7", - "lumiaprism": "1.7", - "mantapacific": "3628.85", - "mantle": "0.888186", - "merlin": "95787", - "metal": "3628.85", - "metis": "60.01", - "mint": "3628.85", - "mode": "3628.85", - "molten": "0.284308", - "moonbeam": "0.313413", - "morph": "3628.85", - "neutron": "0.523303", - "oortmainnet": "0.255252", - "optimism": "3628.85", - "orderly": "3628.85", - "osmosis": "0.589662", - "polygon": "0.621246", - "polygonzkevm": "3628.85", - "polynomialfi": "3628.85", - "prom": "6.5", - "proofofplay": "3628.85", - "rarichain": "3628.85", + "ancient8": "3849.95", + "alephzeroevmmainnet": "0.563568", + "apechain": "1.66", + "appchain": "3849.95", + "arbitrum": "3849.95", + "arbitrumnova": "3849.95", + "astar": "0.078825", + "astarzkevm": "3849.95", + "flame": "7.62", + "avalanche": "49.4", + "b3": "3849.95", + "base": "3849.95", + "bitlayer": "98047", + "blast": "3849.95", + "bob": "3849.95", + "boba": "3849.95", + "bsc": "714.94", + "bsquared": "98047", + "celo": "0.916567", + "cheesechain": "0.0015485", + "chilizmainnet": "0.119182", + "coredao": "1.42", + "cyber": "3849.95", + "degenchain": "0.01880045", + "dogechain": "0.429424", + "duckchain": "6.4", + "eclipsemainnet": "3849.95", + "endurance": "3.21", + "ethereum": "3849.95", + "everclear": "3849.95", + "fantom": "1.23", + "flare": "0.02912373", + "flowmainnet": "1.048", + "fraxtal": "3847.8", + "fusemainnet": "0.04124996", + "gnosis": "1.001", + "gravity": "0.03951512", + "harmony": "0.03939191", + "immutablezkevmmainnet": "1.89", + "inevm": "30.43", + "injective": "30.43", + "kaia": "0.282109", + "kroma": "3849.95", + "linea": "3849.95", + "lisk": "3849.95", + "lukso": "2.81", + "lumia": "2.25", + "lumiaprism": "2.25", + "mantapacific": "3849.95", + "mantle": "1.13", + "merlin": "99320", + "metal": "3849.95", + "metis": "59.74", + "mint": "3849.95", + "mode": "3849.95", + "molten": "0.382952", + "moonbeam": "0.345164", + "morph": "3849.95", + "neutron": "0.571583", + "oortmainnet": "0.22645", + "optimism": "3849.95", + "orderly": "3849.95", + "osmosis": "0.699208", + "polygon": "0.633271", + "polygonzkevm": "3849.95", + "polynomialfi": "3849.95", + "prom": "7.16", + "proofofplay": "3849.95", + "rarichain": "3849.95", "real": "1", - "redstone": "3628.85", - "rootstockmainnet": "95652", - "sanko": "53.86", - "scroll": "3628.85", - "sei": "0.613723", - "shibarium": "0.59728", - "snaxchain": "3628.85", - "solanamainnet": "223.96", - "stride": "0.675504", - "superseed": "3628.85", - "superpositionmainnet": "3628.85", - "swell": "3628.85", - "taiko": "3628.85", + "redstone": "3849.95", + "rootstockmainnet": "98004", + "sanko": "58.27", + "scroll": "3849.95", + "sei": "0.625869", + "shibarium": "0.670964", + "snaxchain": "3849.95", + "solanamainnet": "226.39", + "stride": "0.779753", + "superseed": "3849.95", + "superpositionmainnet": "3849.95", + "swell": "3849.95", + "taiko": "3849.95", "tangle": "1", - "treasure": "0.64326", - "unichain": "3628.85", + "treasure": "0.638598", + "unichain": "3849.95", "vana": "1", - "viction": "0.479042", - "worldchain": "3628.85", - "xai": "0.37142", - "xlayer": "53.81", - "zeronetwork": "3628.85", - "zetachain": "0.819612", - "zircuit": "3628.85", - "zklink": "3628.85", - "zksync": "3628.85", - "zoramainnet": "3628.85" + "viction": "0.50166", + "worldchain": "3849.95", + "xai": "0.368066", + "xlayer": "56.38", + "zeronetwork": "3849.95", + "zetachain": "0.805386", + "zircuit": "3849.95", + "zklink": "3849.95", + "zksync": "3849.95", + "zoramainnet": "3849.95" } diff --git a/typescript/infra/scripts/check/check-utils.ts b/typescript/infra/scripts/check/check-utils.ts index a8289671a0..ef7a2b1215 100644 --- a/typescript/infra/scripts/check/check-utils.ts +++ b/typescript/infra/scripts/check/check-utils.ts @@ -30,6 +30,7 @@ import { DeployEnvironment } from '../../src/config/environment.js'; import { HyperlaneAppGovernor } from '../../src/govern/HyperlaneAppGovernor.js'; import { HyperlaneCoreGovernor } from '../../src/govern/HyperlaneCoreGovernor.js'; import { HyperlaneHaasGovernor } from '../../src/govern/HyperlaneHaasGovernor.js'; +import { HyperlaneICAChecker } from '../../src/govern/HyperlaneICAChecker.js'; import { HyperlaneIgpGovernor } from '../../src/govern/HyperlaneIgpGovernor.js'; import { ProxiedRouterGovernor } from '../../src/govern/ProxiedRouterGovernor.js'; import { Role } from '../../src/roles.js'; @@ -148,7 +149,7 @@ export async function getGovernor( governor = new ProxiedRouterGovernor(checker); } else if (module === Modules.HAAS) { chainsToSkip.forEach((chain) => delete routerConfig[chain]); - const icaChecker = new InterchainAccountChecker( + const icaChecker = new HyperlaneICAChecker( multiProvider, ica, objFilter( diff --git a/typescript/infra/scripts/safes/parse-txs.ts b/typescript/infra/scripts/safes/parse-txs.ts index ac897cd279..21e8f87eed 100644 --- a/typescript/infra/scripts/safes/parse-txs.ts +++ b/typescript/infra/scripts/safes/parse-txs.ts @@ -1,7 +1,12 @@ import { BigNumber } from 'ethers'; import { AnnotatedEV5Transaction } from '@hyperlane-xyz/sdk'; -import { stringifyObject } from '@hyperlane-xyz/utils'; +import { + LogFormat, + LogLevel, + configureRootLogger, + stringifyObject, +} from '@hyperlane-xyz/utils'; import { GovernTransactionReader } from '../../src/tx/govern-transaction-reader.js'; import { getSafeTx } from '../../src/utils/safe.js'; @@ -13,6 +18,8 @@ async function main() { withChainsRequired(getArgs()), ).argv; + configureRootLogger(LogFormat.Pretty, LogLevel.Info); + const config = getEnvironmentConfig(environment); const multiProvider = await config.getMultiProvider(); const { chainAddresses } = await getHyperlaneCore(environment, multiProvider); diff --git a/typescript/infra/src/govern/HyperlaneAppGovernor.ts b/typescript/infra/src/govern/HyperlaneAppGovernor.ts index a704b3d9b8..1d7b5167e3 100644 --- a/typescript/infra/src/govern/HyperlaneAppGovernor.ts +++ b/typescript/infra/src/govern/HyperlaneAppGovernor.ts @@ -486,6 +486,20 @@ export abstract class HyperlaneAppGovernor< await this.checkSubmitterBalance(chain, submitterAddress, call.value); } + // Check if the submitter is the owner of the contract + try { + const ownable = Ownable__factory.connect(call.to, signer); + const owner = await ownable.owner(); + const isOwner = eqAddress(owner, submitterAddress); + + if (!isOwner) { + return false; + } + } catch { + // If the contract does not implement Ownable, just continue + // with the next check. + } + // Check if the transaction has additional success criteria if ( additionalTxSuccessCriteria && diff --git a/typescript/infra/src/govern/HyperlaneHaasGovernor.ts b/typescript/infra/src/govern/HyperlaneHaasGovernor.ts index 921649c5b9..6fa6ebcd18 100644 --- a/typescript/infra/src/govern/HyperlaneHaasGovernor.ts +++ b/typescript/infra/src/govern/HyperlaneHaasGovernor.ts @@ -5,7 +5,6 @@ import { HyperlaneCore, HyperlaneCoreChecker, InterchainAccount, - InterchainAccountChecker, } from '@hyperlane-xyz/sdk'; import { @@ -13,6 +12,7 @@ import { HyperlaneAppGovernor, } from './HyperlaneAppGovernor.js'; import { HyperlaneCoreGovernor } from './HyperlaneCoreGovernor.js'; +import { HyperlaneICAChecker } from './HyperlaneICAChecker.js'; import { ProxiedRouterGovernor } from './ProxiedRouterGovernor.js'; export class HyperlaneHaasGovernor extends HyperlaneAppGovernor< @@ -24,7 +24,7 @@ export class HyperlaneHaasGovernor extends HyperlaneAppGovernor< constructor( ica: InterchainAccount, - private readonly icaChecker: InterchainAccountChecker, + private readonly icaChecker: HyperlaneICAChecker, private readonly coreChecker: HyperlaneCoreChecker, ) { super(coreChecker, ica); diff --git a/typescript/infra/src/govern/HyperlaneICAChecker.ts b/typescript/infra/src/govern/HyperlaneICAChecker.ts new file mode 100644 index 0000000000..cb72bd73a6 --- /dev/null +++ b/typescript/infra/src/govern/HyperlaneICAChecker.ts @@ -0,0 +1,63 @@ +import { + ChainMap, + ChainName, + InterchainAccountChecker, + RouterViolation, + RouterViolationType, +} from '@hyperlane-xyz/sdk'; +import { AddressBytes32, addressToBytes32 } from '@hyperlane-xyz/utils'; + +export class HyperlaneICAChecker extends InterchainAccountChecker { + /* + * Check that the Ethereum router is enrolled correctly, + * and that remote chains have the correct router enrolled. + */ + async checkEthRouterEnrollment(chain: ChainName): Promise { + // If the chain is Ethereum, do the regular full check + if (chain === 'ethereum') { + return super.checkEnrolledRouters(chain); + } + + // Get the Ethereum router address and domain id + const ethereumRouterAddress = this.app.routerAddress('ethereum'); + const ethereumDomainId = this.multiProvider.getDomainId('ethereum'); + // Get the expected Ethereum router address (with padding) + const expectedRouter = addressToBytes32(ethereumRouterAddress); + + // Get the actual Ethereum router address + const router = this.app.router(this.app.getContracts(chain)); + const actualRouter = await router.routers(ethereumDomainId); + + // Check if the actual router address matches the expected router address + if (actualRouter !== expectedRouter) { + const currentRouters: ChainMap = { ethereum: actualRouter }; + const expectedRouters: ChainMap = { + ethereum: expectedRouter, + }; + const routerDiff: ChainMap<{ + actual: AddressBytes32; + expected: AddressBytes32; + }> = { + ethereum: { actual: actualRouter, expected: expectedRouter }, + }; + + const violation: RouterViolation = { + chain, + type: RouterViolationType.EnrolledRouter, + contract: router, + actual: currentRouters, + expected: expectedRouters, + routerDiff, + description: `Ethereum router is not enrolled correctly`, + }; + this.addViolation(violation); + } + } + + async checkChain(chain: ChainName): Promise { + await this.checkMailboxClient(chain); + await this.checkEthRouterEnrollment(chain); + await this.checkProxiedContracts(chain); + await this.checkOwnership(chain); + } +} diff --git a/typescript/infra/src/tx/govern-transaction-reader.ts b/typescript/infra/src/tx/govern-transaction-reader.ts index fadde0f87e..ef82ab1e1a 100644 --- a/typescript/infra/src/tx/govern-transaction-reader.ts +++ b/typescript/infra/src/tx/govern-transaction-reader.ts @@ -8,7 +8,7 @@ import assert from 'assert'; import chalk from 'chalk'; import { BigNumber, ethers } from 'ethers'; -import { TokenRouter__factory } from '@hyperlane-xyz/core'; +import { ProxyAdmin__factory, TokenRouter__factory } from '@hyperlane-xyz/core'; import { AnnotatedEV5Transaction, ChainMap, @@ -123,6 +123,11 @@ export class GovernTransactionReader { return this.readMailboxTransaction(chain, tx); } + // If it's to a Proxy Admin + if (this.isProxyAdminTransaction(chain, tx)) { + return this.readProxyAdminTransaction(chain, tx); + } + // If it's a Multisend if (await this.isMultisendTransaction(chain, tx)) { return this.readMultisendTransaction(chain, tx); @@ -154,6 +159,7 @@ export class GovernTransactionReader { ): boolean { return ( tx.to !== undefined && + this.warpRouteIndex[chain] !== undefined && this.warpRouteIndex[chain][tx.to.toLowerCase()] !== undefined ); } @@ -381,6 +387,43 @@ export class GovernTransactionReader { }; } + private async readProxyAdminTransaction( + chain: ChainName, + tx: AnnotatedEV5Transaction, + ): Promise { + if (!tx.data) { + throw new Error('⚠️ No data in proxyAdmin transaction'); + } + + const proxyAdminInterface = ProxyAdmin__factory.createInterface(); + const decoded = proxyAdminInterface.parseTransaction({ + data: tx.data, + 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}`; + } + + return { + chain, + to: `Proxy Admin (${chain} ${this.chainAddresses[chain].proxyAdmin})`, + insight, + signature: decoded.signature, + args, + }; + } + private ismDerivationsInProgress: ChainMap = {}; private async deriveIsmConfig( @@ -600,6 +643,16 @@ export class GovernTransactionReader { ); } + isProxyAdminTransaction( + chain: ChainName, + tx: AnnotatedEV5Transaction, + ): boolean { + return ( + tx.to !== undefined && + eqAddress(tx.to, this.chainAddresses[chain].proxyAdmin) + ); + } + async isMultisendTransaction( chain: ChainName, tx: AnnotatedEV5Transaction, diff --git a/typescript/sdk/src/consts/multisigIsm.ts b/typescript/sdk/src/consts/multisigIsm.ts index eaa675b327..867059be1d 100644 --- a/typescript/sdk/src/consts/multisigIsm.ts +++ b/typescript/sdk/src/consts/multisigIsm.ts @@ -45,7 +45,7 @@ export const defaultMultisigConfigs: ChainMap = { }, alephzeroevmmainnet: { - threshold: 2, + threshold: 3, validators: [ { address: '0x33f20e6e775747d60301c6ea1c50e51f0389740c', @@ -53,6 +53,10 @@ export const defaultMultisigConfigs: ChainMap = { }, DEFAULT_MERKLY_VALIDATOR, DEFAULT_MITOSIS_VALIDATOR, + { + address: '0xCbf382214825F8c2f347dd4f23F0aDFaFad55dAa', + alias: 'Aleph Zero', + }, ], }, @@ -115,12 +119,14 @@ export const defaultMultisigConfigs: ChainMap = { }, appchain: { - threshold: 1, + threshold: 2, validators: [ { address: '0x0531251bbadc1f9f19ccce3ca6b3f79f08eae1be', alias: AW_VALIDATOR_ALIAS, }, + DEFAULT_MERKLY_VALIDATOR, + DEFAULT_MITOSIS_VALIDATOR, ], }, @@ -888,6 +894,10 @@ export const defaultMultisigConfigs: ChainMap = { address: '0xf0da628f3fb71652d48260bad4691054045832ce', alias: 'Luganodes', }, + { + address: '0xead4141b6ea149901ce4f4b556953f66d04b1d0c', + alias: 'Lisk', + }, ], }, @@ -919,12 +929,14 @@ export const defaultMultisigConfigs: ChainMap = { }, lumiaprism: { - threshold: 1, + threshold: 2, validators: [ { address: '0xb69731640ffd4338a2c9358a935b0274c6463f85', alias: AW_VALIDATOR_ALIAS, }, + DEFAULT_MERKLY_VALIDATOR, + DEFAULT_MITOSIS_VALIDATOR, ], }, @@ -1583,12 +1595,18 @@ export const defaultMultisigConfigs: ChainMap = { }, swell: { - threshold: 1, + threshold: 3, validators: [ { address: '0x4f51e4f4c7fb45d82f91568480a1a2cfb69216ed', alias: AW_VALIDATOR_ALIAS, }, + { + address: '0x9eadf9217be22d9878e0e464727a2176d5c69ff8', + alias: 'Luganodes', + }, + DEFAULT_MERKLY_VALIDATOR, + DEFAULT_MITOSIS_VALIDATOR, ], }, @@ -1624,12 +1642,21 @@ export const defaultMultisigConfigs: ChainMap = { }, treasure: { - threshold: 1, + threshold: 3, validators: [ { address: '0x6ad994819185553e8baa01533f0cd2c7cadfe6cc', alias: AW_VALIDATOR_ALIAS, }, + { + address: '0x278460fa51ff448eb53ffa62951b4b8e3e8f74e3', + alias: 'P2P', + }, + { + address: '0xe92ff70bb463e2aa93426fd2ba51afc39567d426', + alias: 'Treasure', + }, + DEFAULT_MITOSIS_VALIDATOR, ], }, @@ -1666,7 +1693,7 @@ export const defaultMultisigConfigs: ChainMap = { }, vana: { - threshold: 2, + threshold: 3, validators: [ { address: '0xfdf3b0dfd4b822d10cacb15c8ae945ea269e7534', @@ -1674,6 +1701,10 @@ export const defaultMultisigConfigs: ChainMap = { }, DEFAULT_MERKLY_VALIDATOR, DEFAULT_MITOSIS_VALIDATOR, + { + address: '0xba2f4f89cae6863d8b49e4ca0208ed48ad9ac354', + alias: 'P2P', + }, ], }, @@ -1782,12 +1813,14 @@ export const defaultMultisigConfigs: ChainMap = { }, zklink: { - threshold: 1, + threshold: 2, validators: [ { address: '0x217a8cb4789fc45abf56cb6e2ca96f251a5ac181', alias: AW_VALIDATOR_ALIAS, }, + DEFAULT_MERKLY_VALIDATOR, + DEFAULT_MITOSIS_VALIDATOR, ], }, From 6e496b09ff47c6e8544a8055d3c9c792d1828852 Mon Sep 17 00:00:00 2001 From: Danil Nemirovsky Date: Wed, 11 Dec 2024 11:19:11 +0000 Subject: [PATCH 05/18] feat: Enable Solana and Eclipse in Scraper (#4984) ### Description Enable Solana and Eclipse in Scraper. We don't relay messages in Solana and Eclipse testnets, so, we don't enable these testnets in Scraper. ### Related issues - Fixes https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/4274 ### Backward compatibility Yes ### Testing E2E Ethereum and Sealevel test Co-authored-by: Danil Nemirovsky <4614623+ameten@users.noreply.github.com> --- typescript/infra/config/environments/mainnet3/agent.ts | 6 ++---- typescript/infra/config/environments/testnet4/agent.ts | 2 -- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/typescript/infra/config/environments/mainnet3/agent.ts b/typescript/infra/config/environments/mainnet3/agent.ts index 58c42ffd81..ad075baccc 100644 --- a/typescript/infra/config/environments/mainnet3/agent.ts +++ b/typescript/infra/config/environments/mainnet3/agent.ts @@ -276,8 +276,7 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig< degenchain: true, dogechain: true, duckchain: true, - // Cannot scrape Sealevel chains - eclipsemainnet: false, + eclipsemainnet: true, endurance: true, ethereum: true, everclear: true, @@ -328,8 +327,7 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig< sei: true, shibarium: true, snaxchain: true, - // Cannot scrape Sealevel chains - solanamainnet: false, + solanamainnet: true, stride: true, superseed: true, superpositionmainnet: true, diff --git a/typescript/infra/config/environments/testnet4/agent.ts b/typescript/infra/config/environments/testnet4/agent.ts index 4f984ebfe8..2bab1291ac 100644 --- a/typescript/infra/config/environments/testnet4/agent.ts +++ b/typescript/infra/config/environments/testnet4/agent.ts @@ -121,7 +121,6 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig< citreatestnet: true, connextsepolia: false, ecotestnet: true, - // Cannot scrape non-EVM chains eclipsetestnet: false, formtestnet: true, fuji: true, @@ -135,7 +134,6 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig< polygonamoy: true, scrollsepolia: true, sepolia: true, - // Cannot scrape non-EVM chains solanatestnet: false, soneiumtestnet: true, sonictestnet: true, From 4d342db83033ee40bbb86cf1cd28620966fac5e8 Mon Sep 17 00:00:00 2001 From: IvanPsurtcev <87025698+IvanPsurtcev@users.noreply.github.com> Date: Wed, 11 Dec 2024 16:56:57 +0300 Subject: [PATCH 06/18] fix: function write_checkpoint did not write checkpoint_{index}_with_id.json files to the folder (#4915) ### Description Added a mechanism for writing checkpoint files to the checkpoint folder in the write_chekpoint function. Without this change we wrote a lot of files to the root folder, which was not a correct behavior. --- rust/main/hyperlane-base/src/types/gcs_storage.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rust/main/hyperlane-base/src/types/gcs_storage.rs b/rust/main/hyperlane-base/src/types/gcs_storage.rs index a413edb323..93219f8510 100644 --- a/rust/main/hyperlane-base/src/types/gcs_storage.rs +++ b/rust/main/hyperlane-base/src/types/gcs_storage.rs @@ -240,7 +240,8 @@ impl CheckpointSyncer for GcsStorageClient { &self, signed_checkpoint: &SignedCheckpointWithMessageId, ) -> Result<()> { - let object_name = Self::get_checkpoint_key(signed_checkpoint.value.index); + let object_key = Self::get_checkpoint_key(signed_checkpoint.value.index); + let object_name = self.object_path(&object_key); let data = serde_json::to_vec(signed_checkpoint)?; self.upload_and_log(&object_name, data).await } From 17ecb97f894f69c1b07b4f11dac0d64f8f453d63 Mon Sep 17 00:00:00 2001 From: Danil Nemirovsky Date: Wed, 11 Dec 2024 16:38:47 +0000 Subject: [PATCH 07/18] fix: Disable Eclipse in Scraper until we get archival RPC endpoint (#4986) ### Description Disable Eclipse in Scraper until we get archival RPC endpoint ### Related issues - Contributes into https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/4274 ### Backward compatibility Yes ### Testing No testing since it is a config change --------- Co-authored-by: Danil Nemirovsky <4614623+ameten@users.noreply.github.com> --- typescript/infra/config/environments/mainnet3/agent.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/typescript/infra/config/environments/mainnet3/agent.ts b/typescript/infra/config/environments/mainnet3/agent.ts index ad075baccc..9fa5ede3c2 100644 --- a/typescript/infra/config/environments/mainnet3/agent.ts +++ b/typescript/infra/config/environments/mainnet3/agent.ts @@ -276,7 +276,8 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig< degenchain: true, dogechain: true, duckchain: true, - eclipsemainnet: true, + // Disabled until we get archival RPC for Eclipse + eclipsemainnet: false, endurance: true, ethereum: true, everclear: true, From 4515c833c9379f8c75e756ec677560eb860b51d7 Mon Sep 17 00:00:00 2001 From: Trevor Porter Date: Thu, 12 Dec 2024 03:47:13 +0000 Subject: [PATCH 08/18] fix: prioritize higher nonces in the message processor (#4921) ### Description Noticed that the iteration logic doesn't actually prioritize higher nonces. See these example logs https://cloudlogging.app.goo.gl/CAosszzjFzi3uNJM6 when I sent a message right after a relayer restart -- it wouldn't move forward on the high iterator despite there being an unprocessable message on the high iterator, as it was preferring to iterate the low nonce iter through all the processed messages (down to nonce 0). This meant it took a really long time for my message to get relayed! With this new logic, we should see upon startup we actually do prioritize new messages, not just high nonce messages from before the time of restart. Would also be open to maybe moving forward the low and high iter at the same time, but that is a bigger change so I'll leave it like this for now ### Drive-by changes ### Related issues ### Backward compatibility ### Testing --- rust/main/agents/relayer/src/msg/processor.rs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/rust/main/agents/relayer/src/msg/processor.rs b/rust/main/agents/relayer/src/msg/processor.rs index 59ec32cb69..21c6b1def1 100644 --- a/rust/main/agents/relayer/src/msg/processor.rs +++ b/rust/main/agents/relayer/src/msg/processor.rs @@ -86,24 +86,29 @@ impl ForwardBackwardIterator { loop { let high_nonce_message_status = self.high_nonce_iter.try_get_next_nonce(metrics)?; let low_nonce_message_status = self.low_nonce_iter.try_get_next_nonce(metrics)?; - // Always prioritize the high nonce message + match (high_nonce_message_status, low_nonce_message_status) { - // Keep iterating if only processed messages are found + // Always prioritize advancing the the high nonce iterator, as + // we have a preference for higher nonces (MessageStatus::Processed, _) => { self.high_nonce_iter.iterate(); } - (_, MessageStatus::Processed) => { - self.low_nonce_iter.iterate(); - } - // Otherwise return - either a processable message or nothing to process (MessageStatus::Processable(high_nonce_message), _) => { self.high_nonce_iter.iterate(); return Ok(Some(high_nonce_message)); } + + // Low nonce messages are only processed if the high nonce iterator + // can't make any progress + (_, MessageStatus::Processed) => { + self.low_nonce_iter.iterate(); + } (_, MessageStatus::Processable(low_nonce_message)) => { self.low_nonce_iter.iterate(); return Ok(Some(low_nonce_message)); } + + // If both iterators give us unindexed messages, there are no messages at the moment (MessageStatus::Unindexed, MessageStatus::Unindexed) => return Ok(None), } // This loop may iterate through millions of processed messages, blocking the runtime. @@ -157,7 +162,7 @@ impl DirectionalNonceIterator { } fn try_get_next_nonce( - &mut self, + &self, metrics: &MessageProcessorMetrics, ) -> Result> { if let Some(message) = self.indexed_message_with_nonce()? { From f9396fc8143e0d86f261d1b4c3441c3d42a2b622 Mon Sep 17 00:00:00 2001 From: Trevor Porter Date: Thu, 12 Dec 2024 03:50:44 +0000 Subject: [PATCH 09/18] fix: on SVM collateral warp routes, specify the mint when transferring out as readonly (#4905) ### Description - This was surfaced a while back but we never made the tweak -- on the collateral warp routes, the account infos exposed to the relayer informing it how to process a message ask for the token mint to be writeable. This isn't necessary - it can be read-only because nothing is changing in the data of the token mint. A consequence of this is that we need to pay higher fees for accounts that have very high contention (like USDC) - We have strong test coverage here - if this change wasn't possible, our Sealevel tests would fail ### Drive-by changes ### Related issues ### Backward compatibility ### Testing --- .../programs/hyperlane-sealevel-token-collateral/src/plugin.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/sealevel/programs/hyperlane-sealevel-token-collateral/src/plugin.rs b/rust/sealevel/programs/hyperlane-sealevel-token-collateral/src/plugin.rs index 0fc224667e..ff8cf8dd7b 100644 --- a/rust/sealevel/programs/hyperlane-sealevel-token-collateral/src/plugin.rs +++ b/rust/sealevel/programs/hyperlane-sealevel-token-collateral/src/plugin.rs @@ -444,7 +444,7 @@ impl HyperlaneSealevelTokenPlugin for CollateralPlugin { vec![ AccountMeta::new_readonly(token.plugin_data.spl_token_program, false).into(), AccountMeta::new_readonly(spl_associated_token_account::id(), false).into(), - AccountMeta::new(token.plugin_data.mint, false).into(), + AccountMeta::new_readonly(token.plugin_data.mint, false).into(), AccountMeta::new(recipient_associated_token_account, false).into(), AccountMeta::new(ata_payer_account_key, false).into(), AccountMeta::new(token.plugin_data.escrow, false).into(), From 07ffbbea5ba38496c2490df9fe6372068c58c59a Mon Sep 17 00:00:00 2001 From: Trevor Porter Date: Thu, 12 Dec 2024 05:28:37 +0000 Subject: [PATCH 10/18] feat: overrides in the relayer to force collateral warp route mints to be read-only (#4918) ### Description Some context, and the fix for future warp routes: https://github.com/hyperlane-xyz/hyperlane-monorepo/pull/4905 This will force messages to certain collateral warp route recipients to have mint accounts as readonly. This is a first step toward requiring lower fees for some txs. An example of Helius fees for a USDC process that includes the USDC mint as writeable: ``` {"jsonrpc":"2.0","result":{"priorityFeeLevels":{"min":0.0,"low":292041.0,"medium":2207571.0,"high":10405557.0,"veryHigh":22996019.0,"unsafeMax":2000000000.0}},"id":"1"} ``` vs when it's not writeable: ``` {"jsonrpc":"2.0","result":{"priorityFeeLevels":{"min":0.0,"low":0.0,"medium":1.0,"high":150000.0,"veryHigh":10000000.0,"unsafeMax":2000000000.0}},"id":"1"} ``` The alternative to having this PR would be to: - keep marking mints as writeable, forcing us to overpay - or do a program upgrade of the warp routes (unlikely to happen) ### Drive-by changes - Removed `#[allow(warnings)]` from mailbox.rs and made required changes ### Related issues ### Backward compatibility ### Testing --- rust/main/Cargo.lock | 1 + rust/main/Cargo.toml | 1 + .../relayer/src/server/message_retry.rs | 24 +-- rust/main/agents/validator/src/submit.rs | 12 +- .../src/libs/account/tests.rs | 8 +- .../src/providers/cosmos/provider/parse.rs | 2 +- .../main/chains/hyperlane-sealevel/Cargo.toml | 1 + .../chains/hyperlane-sealevel/src/mailbox.rs | 140 +++++++++--------- 8 files changed, 102 insertions(+), 87 deletions(-) diff --git a/rust/main/Cargo.lock b/rust/main/Cargo.lock index 110954d2f7..e29131af94 100644 --- a/rust/main/Cargo.lock +++ b/rust/main/Cargo.lock @@ -4639,6 +4639,7 @@ dependencies = [ "serializable-account-meta", "solana-account-decoder", "solana-client", + "solana-program", "solana-sdk", "solana-transaction-status", "thiserror", diff --git a/rust/main/Cargo.toml b/rust/main/Cargo.toml index 6a0b7cbb35..72e3d6348d 100644 --- a/rust/main/Cargo.toml +++ b/rust/main/Cargo.toml @@ -127,6 +127,7 @@ sha256 = "1.1.4" sha3 = "0.10" solana-account-decoder = "=1.14.13" solana-client = "=1.14.13" +solana-program = "=1.14.13" solana-sdk = "=1.14.13" solana-transaction-status = "=1.14.13" static_assertions = "1.1" diff --git a/rust/main/agents/relayer/src/server/message_retry.rs b/rust/main/agents/relayer/src/server/message_retry.rs index 6d04eed86a..6d160355a5 100644 --- a/rust/main/agents/relayer/src/server/message_retry.rs +++ b/rust/main/agents/relayer/src/server/message_retry.rs @@ -96,9 +96,11 @@ mod tests { let (addr, mut rx) = setup_test_server(); let client = reqwest::Client::new(); - let mut message = HyperlaneMessage::default(); - // Use a random destination domain - message.destination = 42; + let message = HyperlaneMessage { + // Use a random destination domain + destination: 42, + ..Default::default() + }; let pending_operation = MockPendingOperation::with_message_data(message.clone()); let matching_list_body = json!([ { @@ -127,9 +129,11 @@ mod tests { let (addr, mut rx) = setup_test_server(); let client = reqwest::Client::new(); - let mut message = HyperlaneMessage::default(); - // Use a random origin domain - message.origin = 42; + let message = HyperlaneMessage { + // Use a random origin domain + origin: 42, + ..Default::default() + }; let pending_operation = MockPendingOperation::with_message_data(message.clone()); let matching_list_body = json!([ { @@ -216,9 +220,11 @@ mod tests { let (addr, mut rx) = setup_test_server(); let client = reqwest::Client::new(); - let mut message = HyperlaneMessage::default(); - // Use a random origin domain - message.origin = 42; + let message = HyperlaneMessage { + // Use a random origin domain + origin: 42, + ..Default::default() + }; let pending_operation = MockPendingOperation::with_message_data(message.clone()); let matching_list_body = json!([ { diff --git a/rust/main/agents/validator/src/submit.rs b/rust/main/agents/validator/src/submit.rs index 954b8d0d95..f4779c6656 100644 --- a/rust/main/agents/validator/src/submit.rs +++ b/rust/main/agents/validator/src/submit.rs @@ -559,7 +559,7 @@ mod test { let unix_timestamp = chrono::Utc::now().timestamp() as u64; let expected_reorg_period = 12; - let pre_reorg_merke_insertions = vec![ + let pre_reorg_merke_insertions = [ MerkleTreeInsertion::new(0, H256::random()), MerkleTreeInsertion::new(1, H256::random()), MerkleTreeInsertion::new(2, H256::random()), @@ -570,9 +570,9 @@ mod test { } // the last leaf is different post-reorg - let post_reorg_merkle_insertions = vec![ - pre_reorg_merke_insertions[0].clone(), - pre_reorg_merke_insertions[1].clone(), + let post_reorg_merkle_insertions = [ + pre_reorg_merke_insertions[0], + pre_reorg_merke_insertions[1], MerkleTreeInsertion::new(2, H256::random()), ]; let mut mock_onchain_merkle_tree = IncrementalMerkle::default(); @@ -589,9 +589,7 @@ mod test { // the db returns the pre-reorg merkle tree insertions let mut db = MockDb::new(); db.expect_retrieve_merkle_tree_insertion_by_leaf_index() - .returning(move |sequence| { - Ok(Some(pre_reorg_merke_insertions[*sequence as usize].clone())) - }); + .returning(move |sequence| Ok(Some(pre_reorg_merke_insertions[*sequence as usize]))); // boilerplate mocks let mut mock_merkle_tree_hook = MockMerkleTreeHook::new(); diff --git a/rust/main/chains/hyperlane-cosmos/src/libs/account/tests.rs b/rust/main/chains/hyperlane-cosmos/src/libs/account/tests.rs index 0ba8f73d74..1dc7d30331 100644 --- a/rust/main/chains/hyperlane-cosmos/src/libs/account/tests.rs +++ b/rust/main/chains/hyperlane-cosmos/src/libs/account/tests.rs @@ -61,14 +61,14 @@ fn test_ethereum_style() { fn compressed_public_key() -> PublicKey { let hex = hex::decode(COMPRESSED_PUBLIC_KEY).unwrap(); let tendermint = tendermint::PublicKey::from_raw_secp256k1(&hex).unwrap(); - let pub_key = PublicKey::from(tendermint); - pub_key + + PublicKey::from(tendermint) } fn decompressed_public_key() -> PublicKey { let hex = hex::decode(COMPRESSED_PUBLIC_KEY).unwrap(); let decompressed = decompress_public_key(&hex).unwrap(); let tendermint = tendermint::PublicKey::from_raw_secp256k1(&decompressed).unwrap(); - let pub_key = PublicKey::from(tendermint); - pub_key + + PublicKey::from(tendermint) } diff --git a/rust/main/chains/hyperlane-cosmos/src/providers/cosmos/provider/parse.rs b/rust/main/chains/hyperlane-cosmos/src/providers/cosmos/provider/parse.rs index aac9b7ce5a..a2212f6128 100644 --- a/rust/main/chains/hyperlane-cosmos/src/providers/cosmos/provider/parse.rs +++ b/rust/main/chains/hyperlane-cosmos/src/providers/cosmos/provider/parse.rs @@ -153,7 +153,7 @@ mod tests { fn encode_proto(msg: &MsgRecvPacket) -> Any { let mut buf = Vec::with_capacity(msg.encoded_len()); - MsgRecvPacket::encode(&msg, &mut buf).unwrap(); + MsgRecvPacket::encode(msg, &mut buf).unwrap(); Any { type_url: "".to_string(), diff --git a/rust/main/chains/hyperlane-sealevel/Cargo.toml b/rust/main/chains/hyperlane-sealevel/Cargo.toml index 666ebee877..171e7b31ba 100644 --- a/rust/main/chains/hyperlane-sealevel/Cargo.toml +++ b/rust/main/chains/hyperlane-sealevel/Cargo.toml @@ -18,6 +18,7 @@ serde.workspace = true serde_json.workspace = true solana-account-decoder.workspace = true solana-client.workspace = true +solana-program.workspace = true solana-sdk.workspace = true solana-transaction-status.workspace = true thiserror.workspace = true diff --git a/rust/main/chains/hyperlane-sealevel/src/mailbox.rs b/rust/main/chains/hyperlane-sealevel/src/mailbox.rs index d64963e242..66e58131aa 100644 --- a/rust/main/chains/hyperlane-sealevel/src/mailbox.rs +++ b/rust/main/chains/hyperlane-sealevel/src/mailbox.rs @@ -1,6 +1,7 @@ -#![allow(warnings)] // FIXME remove +// Silence a clippy bug https://github.com/rust-lang/rust-clippy/issues/12281 +#![allow(clippy::blocks_in_conditions)] -use std::{collections::HashMap, num::NonZeroU64, ops::RangeInclusive, str::FromStr as _}; +use std::{collections::HashMap, ops::RangeInclusive, str::FromStr as _}; use async_trait::async_trait; use borsh::{BorshDeserialize, BorshSerialize}; @@ -9,10 +10,9 @@ use hyperlane_sealevel_interchain_security_module_interface::{ }; use hyperlane_sealevel_mailbox::{ accounts::{ - DispatchedMessageAccount, Inbox, InboxAccount, OutboxAccount, ProcessedMessage, - ProcessedMessageAccount, DISPATCHED_MESSAGE_DISCRIMINATOR, PROCESSED_MESSAGE_DISCRIMINATOR, + DispatchedMessageAccount, Inbox, InboxAccount, ProcessedMessageAccount, + DISPATCHED_MESSAGE_DISCRIMINATOR, PROCESSED_MESSAGE_DISCRIMINATOR, }, - instruction, instruction::InboxProcess, mailbox_dispatched_message_pda_seeds, mailbox_inbox_pda_seeds, mailbox_outbox_pda_seeds, mailbox_process_authority_pda_seeds, mailbox_processed_message_pda_seeds, @@ -20,54 +20,36 @@ use hyperlane_sealevel_mailbox::{ use hyperlane_sealevel_message_recipient_interface::{ HandleInstruction, MessageRecipientInstruction, }; -use jsonrpc_core::futures_util::TryFutureExt; +use lazy_static::lazy_static; use serializable_account_meta::SimulationReturnData; -use solana_account_decoder::{UiAccountEncoding, UiDataSliceConfig}; -use solana_client::{ - nonblocking::rpc_client::RpcClient, - rpc_client::SerializableTransaction, - rpc_config::{RpcAccountInfoConfig, RpcProgramAccountsConfig, RpcSendTransactionConfig}, - rpc_filter::{Memcmp, MemcmpEncodedBytes, RpcFilterType}, - rpc_response::Response, -}; +use solana_client::{rpc_client::SerializableTransaction, rpc_response::Response}; +use solana_program::pubkey; use solana_sdk::{ account::Account, bs58, clock::Slot, commitment_config::CommitmentConfig, compute_budget::ComputeBudgetInstruction, - hash::Hash, instruction::{AccountMeta, Instruction}, - message::Message, pubkey::Pubkey, signature::Signature, signer::{keypair::Keypair, Signer as _}, - transaction::{Transaction, VersionedTransaction}, -}; -use solana_transaction_status::{ - EncodedConfirmedBlock, EncodedTransaction, EncodedTransactionWithStatusMeta, TransactionStatus, - UiCompiledInstruction, UiConfirmedBlock, UiInnerInstructions, UiInstruction, UiMessage, - UiParsedInstruction, UiReturnDataEncoding, UiTransaction, UiTransactionReturnData, - UiTransactionStatusMeta, + transaction::Transaction, }; +use solana_transaction_status::TransactionStatus; use tracing::{debug, info, instrument, warn}; use hyperlane_core::{ - accumulator::incremental::IncrementalMerkle, config::StrOrIntParseError, BatchItem, - ChainCommunicationError, ChainCommunicationError::ContractError, ChainResult, Checkpoint, - ContractLocator, Decode as _, Encode as _, FixedPointNumber, HyperlaneAbi, HyperlaneChain, - HyperlaneContract, HyperlaneDomain, HyperlaneMessage, HyperlaneProvider, Indexed, Indexer, - KnownHyperlaneDomain, LogMeta, Mailbox, MerkleTreeHook, ReorgPeriod, SequenceAwareIndexer, - TxCostEstimate, TxOutcome, H256, H512, U256, + config::StrOrIntParseError, ChainCommunicationError, ChainResult, ContractLocator, Decode as _, + Encode as _, FixedPointNumber, HyperlaneChain, HyperlaneContract, HyperlaneDomain, + HyperlaneMessage, HyperlaneProvider, Indexed, Indexer, KnownHyperlaneDomain, LogMeta, Mailbox, + MerkleTreeHook, ReorgPeriod, SequenceAwareIndexer, TxCostEstimate, TxOutcome, H256, H512, U256, }; use crate::account::{search_accounts_by_discriminator, search_and_validate_account}; -use crate::error::HyperlaneSealevelError; use crate::log_meta_composer::{ - is_interchain_payment_instruction, is_message_delivery_instruction, - is_message_dispatch_instruction, LogMetaComposer, + is_message_delivery_instruction, is_message_dispatch_instruction, LogMetaComposer, }; -use crate::utils::{decode_h256, decode_h512, from_base58}; use crate::{ConnectionConf, SealevelProvider, SealevelRpcClient}; const SYSTEM_PROGRAM: &str = "11111111111111111111111111111111"; @@ -87,14 +69,33 @@ const PROCESS_DESIRED_PRIORITIZATION_FEE_LAMPORTS_PER_TX: u64 = 500000; /// In micro-lamports. Multiply this by the compute units to figure out /// the additional cost of processing a message, in addition to the mandatory /// "base" cost of signature verification. +/// Unused at the moment, but kept for future reference. +#[allow(dead_code)] const PROCESS_COMPUTE_UNIT_PRICE_MICRO_LAMPORTS: u64 = - ( - // Convert to micro-lamports - (PROCESS_DESIRED_PRIORITIZATION_FEE_LAMPORTS_PER_TX * 1_000_000) - // Divide by the max compute units - / PROCESS_COMPUTE_UNITS as u64 - ); - + // Convert to micro-lamports + (PROCESS_DESIRED_PRIORITIZATION_FEE_LAMPORTS_PER_TX * 1_000_000) + // Divide by the max compute units + / PROCESS_COMPUTE_UNITS as u64; + +// Earlier versions of collateral warp routes were deployed off a version where the mint +// was requested as a writeable account for handle instruction. This is not necessary, +// and generally requires a higher priority fee to be paid. +// This is a HashMap of of (collateral warp route recipient -> mint address) that is +// used to force the mint address to be readonly. +lazy_static! { + static ref RECIPIENT_FORCED_READONLY_ACCOUNTS: HashMap = HashMap::from([ + // EZSOL + (pubkey!("b5pMgizA9vrGRt3hVqnU7vUVGBQUnLpwPzcJhG1ucyQ"), pubkey!("ezSoL6fY1PVdJcJsUpe5CM3xkfmy3zoVCABybm5WtiC")), + // ORCA + (pubkey!("8acihSm2QTGswniKgdgr4JBvJihZ1cakfvbqWCPBLoSp"), pubkey!("orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE")), + // USDC + (pubkey!("3EpVCPUgyjq2MfGeCttyey6bs5zya5wjYZ2BE6yDg6bm"), pubkey!("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v")), + // USDT + (pubkey!("Bk79wMjvpPCh5iQcCEjPWFcG1V2TfgdwaBsWBEYFYSNU"), pubkey!("Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB")), + // WIF + (pubkey!("CuQmsT4eSF4dYiiGUGYYQxJ7c58pUAD5ADE3BbFGzQKx"), pubkey!("EKpQGSJtjMFqKZ9KQanSqYXRcF8fBopzLHYxdM65zcjm")), + ]); +} /// A reference to a Mailbox contract on some Sealevel chain pub struct SealevelMailbox { pub(crate) program_id: Pubkey, @@ -131,13 +132,17 @@ impl SealevelMailbox { }) } + /// Get the Inbox account pubkey and bump seed. pub fn inbox(&self) -> (Pubkey, u8) { self.inbox } + + /// Get the Outbox account pubkey and bump seed. pub fn outbox(&self) -> (Pubkey, u8) { self.outbox } + /// Get the provider RPC client. pub fn rpc(&self) -> &SealevelRpcClient { self.provider.rpc() } @@ -257,14 +262,26 @@ impl SealevelMailbox { message: message.body.clone(), }); - self.get_account_metas_with_instruction_bytes( - recipient_program_id, - &instruction - .encode() - .map_err(ChainCommunicationError::from_other)?, - hyperlane_sealevel_message_recipient_interface::HANDLE_ACCOUNT_METAS_PDA_SEEDS, - ) - .await + let mut account_metas = self + .get_account_metas_with_instruction_bytes( + recipient_program_id, + &instruction + .encode() + .map_err(ChainCommunicationError::from_other)?, + hyperlane_sealevel_message_recipient_interface::HANDLE_ACCOUNT_METAS_PDA_SEEDS, + ) + .await?; + + if let Some(forced_readonly_account) = + RECIPIENT_FORCED_READONLY_ACCOUNTS.get(&recipient_program_id) + { + account_metas + .iter_mut() + .filter(|account_meta| account_meta.pubkey == *forced_readonly_account) + .for_each(|account_meta| account_meta.is_writable = false); + } + + Ok(account_metas) } async fn get_account_metas_with_instruction_bytes( @@ -306,7 +323,8 @@ impl SealevelMailbox { } } - // Stolen from Solana's non-blocking client, but with Jito! + /// Send a transaction to Jito and wait for it to be confirmed. + /// Logic stolen from Solana's non-blocking client. pub async fn send_and_confirm_transaction_with_jito( &self, transaction: &impl SerializableTransaction, @@ -421,7 +439,7 @@ impl HyperlaneContract for SealevelMailbox { impl HyperlaneChain for SealevelMailbox { fn domain(&self) -> &HyperlaneDomain { - &self.provider.domain() + self.provider.domain() } fn provider(&self) -> Box { @@ -664,6 +682,7 @@ pub struct SealevelMailboxIndexer { } impl SealevelMailboxIndexer { + /// Create a new SealevelMailboxIndexer pub fn new( conf: &ConnectionConf, locator: ContractLocator, @@ -694,7 +713,7 @@ impl SealevelMailboxIndexer { } fn rpc(&self) -> &SealevelRpcClient { - &self.mailbox.rpc() + self.mailbox.rpc() } async fn get_dispatched_message_with_nonce( @@ -707,7 +726,7 @@ impl SealevelMailboxIndexer { let accounts = search_accounts_by_discriminator( self.rpc(), &self.program_id, - &DISPATCHED_MESSAGE_DISCRIMINATOR, + DISPATCHED_MESSAGE_DISCRIMINATOR, &nonce_bytes, unique_dispatched_message_pubkey_offset, unique_dispatch_message_pubkey_length, @@ -715,7 +734,7 @@ impl SealevelMailboxIndexer { .await?; let valid_message_storage_pda_pubkey = search_and_validate_account(accounts, |account| { - self.dispatched_message_account(&account) + self.dispatched_message_account(account) })?; // Now that we have the valid message storage PDA pubkey, we can get the full account data. @@ -800,7 +819,7 @@ impl SealevelMailboxIndexer { let accounts = search_accounts_by_discriminator( self.rpc(), &self.program_id, - &PROCESSED_MESSAGE_DISCRIMINATOR, + PROCESSED_MESSAGE_DISCRIMINATOR, &sequence_bytes, delivered_message_id_offset, delivered_message_id_length, @@ -810,7 +829,7 @@ impl SealevelMailboxIndexer { debug!(account_len = ?accounts.len(), "Found accounts with processed message discriminator"); let valid_message_storage_pda_pubkey = search_and_validate_account(accounts, |account| { - self.delivered_message_account(&account) + self.delivered_message_account(account) })?; // Now that we have the valid delivered message storage PDA pubkey, @@ -965,14 +984,3 @@ impl SequenceAwareIndexer for SealevelMailboxIndexer { Ok((Some(sequence), tip)) } } - -struct SealevelMailboxAbi; - -// TODO figure out how this is used and if we can support it for sealevel. -impl HyperlaneAbi for SealevelMailboxAbi { - const SELECTOR_SIZE_BYTES: usize = 8; - - fn fn_map() -> HashMap, &'static str> { - todo!() - } -} From 629b2bccfce968dc868705094de796683a4ff678 Mon Sep 17 00:00:00 2001 From: Danil Nemirovsky Date: Thu, 12 Dec 2024 10:56:43 +0000 Subject: [PATCH 11/18] fix: Re-enable Eclipse in Scraper (#4988) ### Description Re-enable Eclipse in Scraper ### Related issues - Contributes into https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/4271 ### Backward compatibility Yes ### Testing None (config change) Co-authored-by: Danil Nemirovsky <4614623+ameten@users.noreply.github.com> --- typescript/infra/config/environments/mainnet3/agent.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/typescript/infra/config/environments/mainnet3/agent.ts b/typescript/infra/config/environments/mainnet3/agent.ts index 9fa5ede3c2..ad075baccc 100644 --- a/typescript/infra/config/environments/mainnet3/agent.ts +++ b/typescript/infra/config/environments/mainnet3/agent.ts @@ -276,8 +276,7 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig< degenchain: true, dogechain: true, duckchain: true, - // Disabled until we get archival RPC for Eclipse - eclipsemainnet: false, + eclipsemainnet: true, endurance: true, ethereum: true, everclear: true, From 657ac92553e53142d586144a542e1b684dac79a5 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Thu, 12 Dec 2024 10:16:56 -0500 Subject: [PATCH 12/18] fix: suppress help on CLI failures (#4976) ### Description No longer outputs help dialog on any error --- .changeset/long-llamas-fly.md | 5 +++++ typescript/cli/cli.ts | 12 +----------- 2 files changed, 6 insertions(+), 11 deletions(-) create mode 100644 .changeset/long-llamas-fly.md diff --git a/.changeset/long-llamas-fly.md b/.changeset/long-llamas-fly.md new file mode 100644 index 0000000000..c0d6d01e9b --- /dev/null +++ b/.changeset/long-llamas-fly.md @@ -0,0 +1,5 @@ +--- +"@hyperlane-xyz/cli": patch +--- + +Suppress help on CLI failures diff --git a/typescript/cli/cli.ts b/typescript/cli/cli.ts index 45cad33bb8..1485a1ab9f 100644 --- a/typescript/cli/cli.ts +++ b/typescript/cli/cli.ts @@ -34,9 +34,6 @@ import { configureLogger, errorRed } from './src/logger.js'; import { checkVersion } from './src/utils/version-check.js'; import { VERSION } from './src/version.js'; -// From yargs code: -const MISSING_PARAMS_ERROR = 'Not enough non-option arguments'; - console.log(chalk.blue('Hyperlane'), chalk.magentaBright('CLI')); await checkVersion(); @@ -78,14 +75,7 @@ try { .demandCommand() .strict() .help() - .fail((msg, err, yargs) => { - if (msg && !msg.includes(MISSING_PARAMS_ERROR)) errorRed('Error: ' + msg); - console.log(''); - yargs.showHelp(); - console.log(''); - if (err) errorRed(err.toString()); - process.exit(1); - }).argv; + .showHelpOnFail(false).argv; } catch (error: any) { errorRed('Error: ' + error.message); } From c2ca8490d690ba43a18d64d0fecbb31850d4f0cc Mon Sep 17 00:00:00 2001 From: xeno097 Date: Thu, 12 Dec 2024 12:07:37 -0400 Subject: [PATCH 13/18] fix(cli): fix signer init strategy (#4987) ### Description Probably fixes an issue due to a recent pr merge that changes how signers are initialized based on the command to be executed ### Drive-by changes - Updates the key info message log level from info to debug to avoid the following ![image](https://github.com/user-attachments/assets/3ad0d4f8-36b9-4e2f-8c28-199858f8ef0e) ### Related issues ### Backward compatibility - Yes ### Testing - Manual --- .changeset/hot-spies-share.md | 5 ++ typescript/cli/src/commands/signCommands.ts | 3 +- .../strategies/chain/ChainResolverFactory.ts | 12 +-- .../strategies/chain/MultiChainResolver.ts | 76 ++++++++++--------- .../signer/MultiProtocolSignerManager.ts | 6 +- 5 files changed, 58 insertions(+), 44 deletions(-) create mode 100644 .changeset/hot-spies-share.md diff --git a/.changeset/hot-spies-share.md b/.changeset/hot-spies-share.md new file mode 100644 index 0000000000..4a215ae53c --- /dev/null +++ b/.changeset/hot-spies-share.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/cli': minor +--- + +fix signer strategy init for broken cli commands diff --git a/typescript/cli/src/commands/signCommands.ts b/typescript/cli/src/commands/signCommands.ts index 8cfa6d4c1a..8243a10a88 100644 --- a/typescript/cli/src/commands/signCommands.ts +++ b/typescript/cli/src/commands/signCommands.ts @@ -13,7 +13,8 @@ export const SIGN_COMMANDS = [ export function isSignCommand(argv: any): boolean { //TODO: fix reading and checking warp without signer, and remove this const temporarySignCommandsCheck = - argv._[0] === 'warp' && (argv._[1] === 'read' || argv._[1] === 'check'); + argv._[0] === 'warp' && + (argv._[1] === 'read' || argv._[1] === 'check' || argv._[1] === 'verify'); return ( SIGN_COMMANDS.includes(argv._[0]) || (argv._.length > 1 && SIGN_COMMANDS.includes(argv._[1])) || diff --git a/typescript/cli/src/context/strategies/chain/ChainResolverFactory.ts b/typescript/cli/src/context/strategies/chain/ChainResolverFactory.ts index e417ba27b3..8e91417501 100644 --- a/typescript/cli/src/context/strategies/chain/ChainResolverFactory.ts +++ b/typescript/cli/src/context/strategies/chain/ChainResolverFactory.ts @@ -1,7 +1,6 @@ import { CommandType } from '../../../commands/signCommands.js'; import { MultiChainResolver } from './MultiChainResolver.js'; -import { SingleChainResolver } from './SingleChainResolver.js'; import { ChainResolver } from './types.js'; /** @@ -11,13 +10,16 @@ import { ChainResolver } from './types.js'; export class ChainResolverFactory { private static strategyMap: Map ChainResolver> = new Map([ [CommandType.WARP_DEPLOY, () => MultiChainResolver.forWarpRouteConfig()], - [CommandType.WARP_SEND, () => MultiChainResolver.forOriginDestination()], + // Using the forRelayer resolver because warp send allows the user to self relay the tx + [CommandType.WARP_SEND, () => MultiChainResolver.forRelayer()], [CommandType.WARP_APPLY, () => MultiChainResolver.forWarpRouteConfig()], [CommandType.WARP_READ, () => MultiChainResolver.forWarpCoreConfig()], [CommandType.WARP_CHECK, () => MultiChainResolver.forWarpCoreConfig()], - [CommandType.SEND_MESSAGE, () => MultiChainResolver.forOriginDestination()], + // Using the forRelayer resolver because send allows the user to self relay the tx + [CommandType.SEND_MESSAGE, () => MultiChainResolver.forRelayer()], [CommandType.AGENT_KURTOSIS, () => MultiChainResolver.forAgentKurtosis()], - [CommandType.STATUS, () => MultiChainResolver.forOriginDestination()], + // Using the forRelayer resolver because status allows the user to self relay the tx + [CommandType.STATUS, () => MultiChainResolver.forRelayer()], [CommandType.SUBMIT, () => MultiChainResolver.forStrategyConfig()], [CommandType.RELAYER, () => MultiChainResolver.forRelayer()], [CommandType.CORE_APPLY, () => MultiChainResolver.forCoreApply()], @@ -30,7 +32,7 @@ export class ChainResolverFactory { static getStrategy(argv: Record): ChainResolver { const commandKey = `${argv._[0]}:${argv._[1] || ''}`.trim() as CommandType; const createStrategy = - this.strategyMap.get(commandKey) || (() => new SingleChainResolver()); + this.strategyMap.get(commandKey) || (() => MultiChainResolver.default()); return createStrategy(); } } diff --git a/typescript/cli/src/context/strategies/chain/MultiChainResolver.ts b/typescript/cli/src/context/strategies/chain/MultiChainResolver.ts index 64f3257520..8563e9e5d9 100644 --- a/typescript/cli/src/context/strategies/chain/MultiChainResolver.ts +++ b/typescript/cli/src/context/strategies/chain/MultiChainResolver.ts @@ -4,8 +4,9 @@ import { DeployedCoreAddresses, DeployedCoreAddressesSchema, EvmCoreModule, + MultiProvider, } from '@hyperlane-xyz/sdk'; -import { assert } from '@hyperlane-xyz/utils'; +import { ProtocolType, assert } from '@hyperlane-xyz/utils'; import { DEFAULT_WARP_ROUTE_DEPLOYMENT_CONFIG_PATH } from '../../../commands/options.js'; import { readCoreDeployConfigs } from '../../../config/core.js'; @@ -26,13 +27,12 @@ import { getWarpCoreConfigOrExit } from '../../../utils/warp.js'; import { ChainResolver } from './types.js'; enum ChainSelectionMode { - ORIGIN_DESTINATION, AGENT_KURTOSIS, WARP_CONFIG, WARP_READ, STRATEGY, - RELAYER, CORE_APPLY, + DEFAULT, } // This class could be broken down into multiple strategies @@ -54,13 +54,11 @@ export class MultiChainResolver implements ChainResolver { return this.resolveAgentChains(argv); case ChainSelectionMode.STRATEGY: return this.resolveStrategyChains(argv); - case ChainSelectionMode.RELAYER: - return this.resolveRelayerChains(argv); case ChainSelectionMode.CORE_APPLY: return this.resolveCoreApplyChains(argv); - case ChainSelectionMode.ORIGIN_DESTINATION: + case ChainSelectionMode.DEFAULT: default: - return this.resolveOriginDestinationChains(argv); + return this.resolveRelayerChains(argv); } } @@ -119,28 +117,6 @@ export class MultiChainResolver implements ChainResolver { return [argv.origin, ...argv.targets]; } - private async resolveOriginDestinationChains( - argv: Record, - ): Promise { - const { chainMetadata } = argv.context; - - argv.origin = - argv.origin ?? - (await runSingleChainSelectionStep( - chainMetadata, - 'Select the origin chain', - )); - - argv.destination = - argv.destination ?? - (await runSingleChainSelectionStep( - chainMetadata, - 'Select the destination chain', - )); - - return [argv.origin, argv.destination]; - } - private async resolveStrategyChains( argv: Record, ): Promise { @@ -151,7 +127,29 @@ export class MultiChainResolver implements ChainResolver { private async resolveRelayerChains( argv: Record, ): Promise { - return argv.chains.split(',').map((item: string) => item.trim()); + const { multiProvider } = argv.context; + const chains = []; + + if (argv.origin) { + chains.push(argv.origin); + } + + if (argv.destination) { + chains.push(argv.destination); + } + + if (!argv.chains) { + return Array.from( + new Set([...chains, ...this.getEvmChains(multiProvider)]), + ); + } + + return Array.from( + new Set([ + ...chains, + ...argv.chains.split(',').map((item: string) => item.trim()), + ]), + ); } private async getWarpRouteConfigChains( @@ -219,16 +217,20 @@ export class MultiChainResolver implements ChainResolver { } } - static forAgentKurtosis(): MultiChainResolver { - return new MultiChainResolver(ChainSelectionMode.AGENT_KURTOSIS); + private getEvmChains(multiProvider: MultiProvider): ChainName[] { + const chains = multiProvider.getKnownChainNames(); + + return chains.filter( + (chain) => multiProvider.getProtocol(chain) === ProtocolType.Ethereum, + ); } - static forOriginDestination(): MultiChainResolver { - return new MultiChainResolver(ChainSelectionMode.ORIGIN_DESTINATION); + static forAgentKurtosis(): MultiChainResolver { + return new MultiChainResolver(ChainSelectionMode.AGENT_KURTOSIS); } static forRelayer(): MultiChainResolver { - return new MultiChainResolver(ChainSelectionMode.RELAYER); + return new MultiChainResolver(ChainSelectionMode.DEFAULT); } static forStrategyConfig(): MultiChainResolver { @@ -246,4 +248,8 @@ export class MultiChainResolver implements ChainResolver { static forCoreApply(): MultiChainResolver { return new MultiChainResolver(ChainSelectionMode.CORE_APPLY); } + + static default(): MultiChainResolver { + return new MultiChainResolver(ChainSelectionMode.DEFAULT); + } } diff --git a/typescript/cli/src/context/strategies/signer/MultiProtocolSignerManager.ts b/typescript/cli/src/context/strategies/signer/MultiProtocolSignerManager.ts index 12f9c0f819..f8e411bad1 100644 --- a/typescript/cli/src/context/strategies/signer/MultiProtocolSignerManager.ts +++ b/typescript/cli/src/context/strategies/signer/MultiProtocolSignerManager.ts @@ -118,12 +118,12 @@ export class MultiProtocolSignerManager { let privateKey: string; if (this.options.key) { - this.logger.info( + this.logger.debug( `Using private key passed via CLI --key flag for chain ${chain}`, ); privateKey = this.options.key; } else if (ENV.HYP_KEY) { - this.logger.info(`Using private key from .env for chain ${chain}`); + this.logger.debug(`Using private key from .env for chain ${chain}`); privateKey = ENV.HYP_KEY; } else { privateKey = await this.extractPrivateKey(chain, signerStrategy); @@ -145,7 +145,7 @@ export class MultiProtocolSignerManager { `No private key found for chain ${chain}`, ); - this.logger.info( + this.logger.debug( `Extracting private key from strategy config/user prompt for chain ${chain}`, ); return strategyConfig.privateKey; From c3510265c28d2e5f8fed58ba9a56ebac2b8ada2a Mon Sep 17 00:00:00 2001 From: Trevor Porter Date: Thu, 12 Dec 2024 16:35:51 +0000 Subject: [PATCH 14/18] feat: add treasure batch contract address (#4991) ### Description - Multicall3 is at a nonstandard address due to it being a zksync chain ### Drive-by changes ### Related issues ### Backward compatibility ### Testing --- rust/main/config/mainnet_config.json | 1 + 1 file changed, 1 insertion(+) diff --git a/rust/main/config/mainnet_config.json b/rust/main/config/mainnet_config.json index dc66d22345..c7fb366bd9 100644 --- a/rust/main/config/mainnet_config.json +++ b/rust/main/config/mainnet_config.json @@ -6322,6 +6322,7 @@ "validatorAnnounce": "0x1196055C61af3e3DA6f8458B07b255a72b64Bcf7" }, "treasure": { + "batchContractAddress": "0x2e29fe39496a56856D8698bD43e1dF4D0CE6266a", "blockExplorers": [ { "apiUrl": "https://rpc-explorer-verify.treasure.lol/contract_verification", From fd20bb1e92001bbbb34061821133a21909719d08 Mon Sep 17 00:00:00 2001 From: Lee <6251863+ltyu@users.noreply.github.com> Date: Thu, 12 Dec 2024 16:52:01 -0500 Subject: [PATCH 15/18] feat: Add FeeHook and Swell to pzEth (#4974) ### Description ### Drive-by changes ### Related issues ### Backward compatibility ### Testing --------- Co-authored-by: Trevor Porter --- .changeset/dull-pianos-kiss.md | 9 ++ .registryrc | 2 +- rust/main/config/mainnet_config.json | 3 + typescript/cli/package.json | 2 +- typescript/helloworld/package.json | 2 +- .../configGetters/getRenzoEZETHWarpConfig.ts | 115 +++++++++++++++++- .../configGetters/getRenzoPZETHWarpConfig.ts | 40 +++++- .../environments/mainnet3/warp/warpIds.ts | 4 +- typescript/infra/config/warp.ts | 4 +- typescript/infra/package.json | 2 +- .../sdk/src/router/HyperlaneRouterChecker.ts | 10 +- typescript/widgets/package.json | 2 +- yarn.lock | 16 +-- 13 files changed, 183 insertions(+), 28 deletions(-) create mode 100644 .changeset/dull-pianos-kiss.md diff --git a/.changeset/dull-pianos-kiss.md b/.changeset/dull-pianos-kiss.md new file mode 100644 index 0000000000..333a748904 --- /dev/null +++ b/.changeset/dull-pianos-kiss.md @@ -0,0 +1,9 @@ +--- +'@hyperlane-xyz/helloworld': minor +'@hyperlane-xyz/widgets': minor +'@hyperlane-xyz/infra': minor +'@hyperlane-xyz/cli': minor +'@hyperlane-xyz/sdk': minor +--- + +Add FeeHook and Swell to pz and ez eth config generator. Bump up Registry 6.6.0 diff --git a/.registryrc b/.registryrc index ed389daa94..15e15930d3 100644 --- a/.registryrc +++ b/.registryrc @@ -1 +1 @@ -c7891cdf0fc6a1541c41e19251611c9152ee8bf9 +bde63f7c32e8d169d7e3163b14b5bb25bd3d5042 diff --git a/rust/main/config/mainnet_config.json b/rust/main/config/mainnet_config.json index c7fb366bd9..bb97daa396 100644 --- a/rust/main/config/mainnet_config.json +++ b/rust/main/config/mainnet_config.json @@ -925,6 +925,9 @@ "protocolFee": "0x8B05BF30F6247a90006c5837eA63C7905D79e6d8", "proxyAdmin": "0x75EE15Ee1B4A75Fa3e2fDF5DF3253c25599cc659", "rpcUrls": [ + { + "http": "https://rpc.ankr.com/eth" + }, { "http": "https://ethereum.publicnode.com" }, diff --git a/typescript/cli/package.json b/typescript/cli/package.json index 48f7d80596..7940f8e756 100644 --- a/typescript/cli/package.json +++ b/typescript/cli/package.json @@ -5,7 +5,7 @@ "dependencies": { "@aws-sdk/client-kms": "^3.577.0", "@aws-sdk/client-s3": "^3.577.0", - "@hyperlane-xyz/registry": "6.3.0", + "@hyperlane-xyz/registry": "6.6.0", "@hyperlane-xyz/sdk": "7.3.0", "@hyperlane-xyz/utils": "7.3.0", "@inquirer/core": "9.0.10", diff --git a/typescript/helloworld/package.json b/typescript/helloworld/package.json index a7fdc9bb28..83bc100ac3 100644 --- a/typescript/helloworld/package.json +++ b/typescript/helloworld/package.json @@ -4,7 +4,7 @@ "version": "7.3.0", "dependencies": { "@hyperlane-xyz/core": "5.8.3", - "@hyperlane-xyz/registry": "6.3.0", + "@hyperlane-xyz/registry": "6.6.0", "@hyperlane-xyz/sdk": "7.3.0", "@openzeppelin/contracts-upgradeable": "^4.9.3", "ethers": "^5.7.2" diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getRenzoEZETHWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getRenzoEZETHWarpConfig.ts index 8928f2fb58..93ea7df1ef 100644 --- a/typescript/infra/config/environments/mainnet3/warp/configGetters/getRenzoEZETHWarpConfig.ts +++ b/typescript/infra/config/environments/mainnet3/warp/configGetters/getRenzoEZETHWarpConfig.ts @@ -1,15 +1,24 @@ +import { parseEther } from 'ethers/lib/utils.js'; + +import { Mailbox__factory } from '@hyperlane-xyz/core'; import { ChainMap, + ChainName, + HookConfig, + HookType, HypTokenRouterConfig, IsmType, MultisigConfig, TokenType, buildAggregationIsmConfigs, } from '@hyperlane-xyz/sdk'; -import { symmetricDifference } from '@hyperlane-xyz/utils'; +import { Address, assert, symmetricDifference } from '@hyperlane-xyz/utils'; +import { getEnvironmentConfig } from '../../../../../scripts/core-utils.js'; import { getRegistry as getMainnet3Registry } from '../../chains.js'; +import rawTokenPrices from '../../tokenPrices.json'; +const tokenPrices: ChainMap = rawTokenPrices; const chainsToDeploy = [ 'arbitrum', 'optimism', @@ -23,7 +32,33 @@ const chainsToDeploy = [ 'zircuit', 'taiko', 'sei', + 'swell', ]; +export const MAX_PROTOCOL_FEE = parseEther('100').toString(); // Changing this will redeploy the PROTOCOL_FEE hook + +export function getProtocolFee(chain: ChainName) { + return (0.5 / Number(tokenPrices[chain])).toFixed(10).toString(); // ~$0.50 USD +} + +export function getRenzoHook( + defaultHook: Address, + chain: ChainName, +): HookConfig { + return { + type: HookType.AGGREGATION, + hooks: [ + defaultHook, + { + type: HookType.PROTOCOL_FEE, + owner: ezEthSafes[chain], + beneficiary: ezEthSafes[chain], + protocolFee: parseEther(getProtocolFee(chain)).toString(), + maxProtocolFee: MAX_PROTOCOL_FEE, + }, + ], + }; +} + const lockboxChain = 'ethereum'; // over the default 100k to account for xerc20 gas + ISM overhead over the default ISM https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/49f41d9759fd515bfd89e6e22e799c41b27b4119/typescript/sdk/src/router/GasRouterDeployer.ts#L14 const warpRouteOverheadGas = 200_000; @@ -41,6 +76,7 @@ const xERC20: Record<(typeof chainsToDeploy)[number], string> = { zircuit: '0x2416092f143378750bb29b79eD961ab195CcEea5', taiko: '0x2416092f143378750bb29b79eD961ab195CcEea5', sei: '0x6DCfbF4729890043DFd34A93A2694E5303BA2703', // redEth + swell: '0x2416092f143378750bb29b79eD961ab195CcEea5', }; export const ezEthValidators: ChainMap = { @@ -164,6 +200,16 @@ export const ezEthValidators: ChainMap = { { address: '0x952df7f0cb8611573a53dd7cbf29768871d9f8b0', alias: 'Renzo' }, ], }, + swell: { + threshold: 1, + validators: [ + { + address: '0x9eadf9217be22d9878e0e464727a2176d5c69ff8', + alias: 'Luganodes', + }, + { address: '0xb6b9b4bd4eb6eb3aef5e9826e7f8b8455947f67c', alias: 'Renzo' }, + ], + }, }; export const ezEthSafes: Record = { @@ -179,11 +225,65 @@ export const ezEthSafes: Record = { zircuit: '0x8410927C286A38883BC23721e640F31D3E3E79F8', taiko: '0x8410927C286A38883BC23721e640F31D3E3E79F8', sei: '0x0e60fd361fF5b90088e1782e6b21A7D177d462C5', + swell: '0x435E8c9652Da151292F3981bbf663EBEB6668501', +}; + +const existingProxyAdmins: ChainMap<{ address: string; owner: string }> = { + arbitrum: { + address: '0xdcB558d5C0F9A35C53Fa343c77eD0d346576e2Cf', + owner: ezEthSafes.arbitrum, + }, + optimism: { + address: '0xa50910ae66Df6A5F8e85dac032FD45BC2b7be6fF', + owner: ezEthSafes.optimism, + }, + base: { + address: '0xec1DdF05ff85D2B22B7d27E5b5E0B82961B7D889', + owner: ezEthSafes.base, + }, + blast: { + address: '0xA26F8cE2E21A503bf9e18c213965d7BC14997F48', + owner: ezEthSafes.blast, + }, + bsc: { + address: '0x486b39378f99f073A3043C6Aabe8666876A8F3C5', + owner: ezEthSafes.bsc, + }, + mode: { + address: '0x2F78F22a1D7491500C9ED9352b8239fbAbcDd84E', + owner: ezEthSafes.mode, + }, + fraxtal: { + address: '0x8bB69721B4E9b9df08bEdaeaA193008C7317Db59', + owner: ezEthSafes.fraxtal, + }, + linea: { + address: '0x2F78F22a1D7491500C9ED9352b8239fbAbcDd84E', + owner: ezEthSafes.linea, + }, + ethereum: { + address: '0x2F78F22a1D7491500C9ED9352b8239fbAbcDd84E', + owner: ezEthSafes.ethereum, + }, + zircuit: { + address: '0xec1DdF05ff85D2B22B7d27E5b5E0B82961B7D889', + owner: ezEthSafes.zircuit, + }, + sei: { + address: '0x33219fEF24C198d979F05d692a17507E41a0A73e', + owner: ezEthSafes.sei, + }, + taiko: { + address: '0xA3666f8a327AADB666F1906A38B17937e5F11f92', + owner: ezEthSafes.taiko, + }, }; export const getRenzoEZETHWarpConfig = async (): Promise< ChainMap > => { + const config = getEnvironmentConfig('mainnet3'); + const multiProvider = await config.getMultiProvider(); const registry = await getMainnet3Registry(); const validatorDiff = symmetricDifference( @@ -222,6 +322,15 @@ export const getRenzoEZETHWarpConfig = async (): Promise< await Promise.all( chainsToDeploy.map( async (chain): Promise<[string, HypTokenRouterConfig]> => { + const addresses = await registry.getChainAddresses(chain); + assert(addresses, 'No addresses in Registry'); + const { mailbox } = addresses; + + const mailboxContract = Mailbox__factory.connect( + mailbox, + multiProvider.getProvider(chain), + ); + const defaultHook = await mailboxContract.defaultHook(); const ret: [string, HypTokenRouterConfig] = [ chain, { @@ -233,7 +342,7 @@ export const getRenzoEZETHWarpConfig = async (): Promise< token: chain === lockboxChain ? lockbox : xERC20[chain], owner: ezEthSafes[chain], gas: warpRouteOverheadGas, - mailbox: (await registry.getChainAddresses(chain))!.mailbox, + mailbox, interchainSecurityModule: { type: IsmType.AGGREGATION, threshold: 2, @@ -254,6 +363,8 @@ export const getRenzoEZETHWarpConfig = async (): Promise< }, ], }, + hook: getRenzoHook(defaultHook, chain), + proxyAdmin: existingProxyAdmins[chain], }, ]; diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getRenzoPZETHWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getRenzoPZETHWarpConfig.ts index 9c269f5413..6196031a8e 100644 --- a/typescript/infra/config/environments/mainnet3/warp/configGetters/getRenzoPZETHWarpConfig.ts +++ b/typescript/infra/config/environments/mainnet3/warp/configGetters/getRenzoPZETHWarpConfig.ts @@ -1,3 +1,4 @@ +import { Mailbox__factory } from '@hyperlane-xyz/core'; import { ChainMap, HypTokenRouterConfig, @@ -5,11 +6,16 @@ import { TokenType, buildAggregationIsmConfigs, } from '@hyperlane-xyz/sdk'; -import { symmetricDifference } from '@hyperlane-xyz/utils'; +import { assert, symmetricDifference } from '@hyperlane-xyz/utils'; +import { getEnvironmentConfig } from '../../../../../scripts/core-utils.js'; import { getRegistry as getMainnet3Registry } from '../../chains.js'; -import { ezEthSafes, ezEthValidators } from './getRenzoEZETHWarpConfig.js'; +import { + ezEthSafes, + ezEthValidators, + getRenzoHook, +} from './getRenzoEZETHWarpConfig.js'; const lockbox = '0xbC5511354C4A9a50DE928F56DB01DD327c4e56d5'; const xERC20 = '0x9cb41CD74D01ae4b4f640EC40f7A60cA1bCF83E7'; @@ -17,21 +23,36 @@ const lockboxChain = 'ethereum'; // over the default 100k to account for xerc20 gas + ISM overhead over the default ISM https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/49f41d9759fd515bfd89e6e22e799c41b27b4119/typescript/sdk/src/router/GasRouterDeployer.ts#L14 const warpRouteOverheadGas = 200_000; -const chainsToDeploy = ['ethereum', 'zircuit']; +const chainsToDeploy = ['ethereum', 'swell', 'zircuit']; const pzEthValidators = { ethereum: ezEthValidators.ethereum, zircuit: ezEthValidators.zircuit, + swell: ezEthValidators.swell, }; const pzEthSafes: Record = { ethereum: ezEthSafes.ethereum, zircuit: ezEthSafes.zircuit, + swell: ezEthSafes.swell, +}; + +const existingProxyAdmins: ChainMap<{ address: string; owner: string }> = { + ethereum: { + address: '0x4f4671Ce69c9af15e33eB7Cf6D1358d1B39Af3bF', + owner: '0xD1e6626310fD54Eceb5b9a51dA2eC329D6D4B68A', + }, + zircuit: { + address: '0x8b789B4A56675240c9f0985B467752b870c75711', + owner: '0x8410927C286A38883BC23721e640F31D3E3E79F8', + }, }; export const getRenzoPZETHWarpConfig = async (): Promise< ChainMap > => { + const config = getEnvironmentConfig('mainnet3'); + const multiProvider = await config.getMultiProvider(); const registry = await getMainnet3Registry(); const validatorDiff = symmetricDifference( @@ -59,6 +80,15 @@ export const getRenzoPZETHWarpConfig = async (): Promise< await Promise.all( chainsToDeploy.map( async (chain): Promise<[string, HypTokenRouterConfig]> => { + const addresses = await registry.getChainAddresses(chain); + assert(addresses, 'No addresses in Registry'); + const { mailbox } = addresses; + + const mailboxContract = Mailbox__factory.connect( + mailbox, + multiProvider.getProvider(chain), + ); + const defaultHook = await mailboxContract.defaultHook(); const ret: [string, HypTokenRouterConfig] = [ chain, { @@ -70,7 +100,7 @@ export const getRenzoPZETHWarpConfig = async (): Promise< token: chain === lockboxChain ? lockbox : xERC20, owner: pzEthSafes[chain], gas: warpRouteOverheadGas, - mailbox: (await registry.getChainAddresses(chain))!.mailbox, + mailbox, interchainSecurityModule: { type: IsmType.AGGREGATION, threshold: 2, @@ -91,6 +121,8 @@ export const getRenzoPZETHWarpConfig = async (): Promise< }, ], }, + hook: getRenzoHook(defaultHook, chain), + proxyAdmin: existingProxyAdmins[chain], }, ]; diff --git a/typescript/infra/config/environments/mainnet3/warp/warpIds.ts b/typescript/infra/config/environments/mainnet3/warp/warpIds.ts index 3d23bdc087..685567e4ad 100644 --- a/typescript/infra/config/environments/mainnet3/warp/warpIds.ts +++ b/typescript/infra/config/environments/mainnet3/warp/warpIds.ts @@ -1,6 +1,6 @@ export enum WarpRouteIds { Ancient8EthereumUSDC = 'USDC/ancient8-ethereum', - ArbitrumBaseBlastBscEthereumFraxtalLineaModeOptimismSeiTaikoZircuitEZETH = 'EZETH/arbitrum-base-blast-bsc-ethereum-fraxtal-linea-mode-optimism-sei-taiko-zircuit', + ArbitrumBaseBlastBscEthereumFraxtalLineaModeOptimismSeiSwellTaikoZircuitEZETH = 'EZETH/arbitrum-base-blast-bsc-ethereum-fraxtal-linea-mode-optimism-sei-swell-taiko-zircuit', ArbitrumBaseEnduranceUSDC = 'USDC/arbitrum-base-endurance', ArbitrumEthereumZircuitAMPHRETH = 'AMPHRETH/arbitrum-ethereum-zircuit', ArbitrumNeutronEclip = 'ECLIP/arbitrum-neutron', @@ -24,7 +24,7 @@ export enum WarpRouteIds { EthereumVictionETH = 'ETH/ethereum-viction', EthereumVictionUSDC = 'USDC/ethereum-viction', EthereumVictionUSDT = 'USDT/ethereum-viction', - EthereumZircuitPZETH = 'PZETH/ethereum-zircuit', + EthereumSwellZircuitPZETH = 'PZETH/ethereum-swell-zircuit', EthereumBscLumiaLUMIA = 'LUMIA/bsc-ethereum-lumia', InevmInjectiveINJ = 'INJ/inevm-injective', MantapacificNeutronTIA = 'TIA/mantapacific-neutron', diff --git a/typescript/infra/config/warp.ts b/typescript/infra/config/warp.ts index 5c9049eb7d..f9ebeb69c6 100644 --- a/typescript/infra/config/warp.ts +++ b/typescript/infra/config/warp.ts @@ -53,7 +53,7 @@ export const warpConfigGetterMap: Record = { [WarpRouteIds.EthereumInevmUSDT]: getEthereumInevmUSDTWarpConfig, [WarpRouteIds.ArbitrumNeutronEclip]: getArbitrumNeutronEclipWarpConfig, [WarpRouteIds.ArbitrumNeutronTIA]: getArbitrumNeutronTiaWarpConfig, - [WarpRouteIds.ArbitrumBaseBlastBscEthereumFraxtalLineaModeOptimismSeiTaikoZircuitEZETH]: + [WarpRouteIds.ArbitrumBaseBlastBscEthereumFraxtalLineaModeOptimismSeiSwellTaikoZircuitEZETH]: getRenzoEZETHWarpConfig, [WarpRouteIds.InevmInjectiveINJ]: getInevmInjectiveINJWarpConfig, [WarpRouteIds.EthereumFlowCbBTC]: getEthereumFlowCbBTCWarpConfig, @@ -61,7 +61,7 @@ export const warpConfigGetterMap: Record = { [WarpRouteIds.EthereumVictionETH]: getEthereumVictionETHWarpConfig, [WarpRouteIds.EthereumVictionUSDC]: getEthereumVictionUSDCWarpConfig, [WarpRouteIds.EthereumVictionUSDT]: getEthereumVictionUSDTWarpConfig, - [WarpRouteIds.EthereumZircuitPZETH]: getRenzoPZETHWarpConfig, + [WarpRouteIds.EthereumSwellZircuitPZETH]: getRenzoPZETHWarpConfig, [WarpRouteIds.EthereumBscLumiaLUMIA]: getEthereumBscLUMIAWarpConfig, [WarpRouteIds.MantapacificNeutronTIA]: getMantapacificNeutronTiaWarpConfig, [WarpRouteIds.EclipseEthereumApxEth]: getEclipseEthereumApxEthWarpConfig, diff --git a/typescript/infra/package.json b/typescript/infra/package.json index 6f67d4ae23..a1aebb8998 100644 --- a/typescript/infra/package.json +++ b/typescript/infra/package.json @@ -14,7 +14,7 @@ "@ethersproject/providers": "*", "@google-cloud/secret-manager": "^5.5.0", "@hyperlane-xyz/helloworld": "7.3.0", - "@hyperlane-xyz/registry": "6.3.0", + "@hyperlane-xyz/registry": "6.6.0", "@hyperlane-xyz/sdk": "7.3.0", "@hyperlane-xyz/utils": "7.3.0", "@inquirer/prompts": "3.3.2", diff --git a/typescript/sdk/src/router/HyperlaneRouterChecker.ts b/typescript/sdk/src/router/HyperlaneRouterChecker.ts index abdc5c8852..e514c8522e 100644 --- a/typescript/sdk/src/router/HyperlaneRouterChecker.ts +++ b/typescript/sdk/src/router/HyperlaneRouterChecker.ts @@ -3,7 +3,6 @@ import { ethers } from 'ethers'; import { AddressBytes32, addressToBytes32, - assert, eqAddress, isZeroishAddress, rootLogger, @@ -68,10 +67,11 @@ export class HyperlaneRouterChecker< } if (config.hook) { - assert( - typeof config.hook === 'string', - 'Hook objects not supported in router checker', - ); + if (typeof config.hook !== 'string') + return this.logger.info( + `Hook objects not supported in router checker for HookConfig: ${config.hook}`, + ); + const hook = await router.hook(); if (!eqAddress(hook, config.hook as string)) { this.addViolation({ diff --git a/typescript/widgets/package.json b/typescript/widgets/package.json index f662fa2a29..701ff8ddd8 100644 --- a/typescript/widgets/package.json +++ b/typescript/widgets/package.json @@ -27,7 +27,7 @@ "@emotion/react": "^11.13.3", "@emotion/styled": "^11.13.0", "@eslint/js": "^9.15.0", - "@hyperlane-xyz/registry": "6.3.0", + "@hyperlane-xyz/registry": "6.6.0", "@storybook/addon-essentials": "^7.6.14", "@storybook/addon-interactions": "^7.6.14", "@storybook/addon-links": "^7.6.14", diff --git a/yarn.lock b/yarn.lock index 91d745e473..70a2c8f27f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7367,7 +7367,7 @@ __metadata: "@eslint/js": "npm:^9.15.0" "@ethersproject/abi": "npm:*" "@ethersproject/providers": "npm:*" - "@hyperlane-xyz/registry": "npm:6.3.0" + "@hyperlane-xyz/registry": "npm:6.6.0" "@hyperlane-xyz/sdk": "npm:7.3.0" "@hyperlane-xyz/utils": "npm:7.3.0" "@inquirer/core": "npm:9.0.10" @@ -7469,7 +7469,7 @@ __metadata: dependencies: "@eslint/js": "npm:^9.15.0" "@hyperlane-xyz/core": "npm:5.8.3" - "@hyperlane-xyz/registry": "npm:6.3.0" + "@hyperlane-xyz/registry": "npm:6.6.0" "@hyperlane-xyz/sdk": "npm:7.3.0" "@nomiclabs/hardhat-ethers": "npm:^2.2.3" "@nomiclabs/hardhat-waffle": "npm:^2.0.6" @@ -7520,7 +7520,7 @@ __metadata: "@ethersproject/providers": "npm:*" "@google-cloud/secret-manager": "npm:^5.5.0" "@hyperlane-xyz/helloworld": "npm:7.3.0" - "@hyperlane-xyz/registry": "npm:6.3.0" + "@hyperlane-xyz/registry": "npm:6.6.0" "@hyperlane-xyz/sdk": "npm:7.3.0" "@hyperlane-xyz/utils": "npm:7.3.0" "@inquirer/prompts": "npm:3.3.2" @@ -7582,13 +7582,13 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/registry@npm:6.3.0": - version: 6.3.0 - resolution: "@hyperlane-xyz/registry@npm:6.3.0" +"@hyperlane-xyz/registry@npm:6.6.0": + version: 6.6.0 + resolution: "@hyperlane-xyz/registry@npm:6.6.0" dependencies: yaml: "npm:2.4.5" zod: "npm:^3.21.2" - checksum: 10/7a1b7226593edf8e12c7e3d425b7889679d46abae1e8c5a389a0b472e2e0a08b292ba89f60572eff9e844c7b5f7322e4420a8888fabef9967c206d5b39f391a1 + checksum: 10/4e8c955054a3872439f8a52ba208db848da1e46cdf920d4c0cf42080a147c4185d6fed2b00dec0320dfebeb7287578c8a95d3f2f81c2ba713a83a426a1793525 languageName: node linkType: hard @@ -7688,7 +7688,7 @@ __metadata: "@emotion/styled": "npm:^11.13.0" "@eslint/js": "npm:^9.15.0" "@headlessui/react": "npm:^2.1.8" - "@hyperlane-xyz/registry": "npm:6.3.0" + "@hyperlane-xyz/registry": "npm:6.6.0" "@hyperlane-xyz/sdk": "npm:7.3.0" "@hyperlane-xyz/utils": "npm:7.3.0" "@interchain-ui/react": "npm:^1.23.28" From 0eb8d52a4490864d1bb9ce04a9eed9e1ebc33bf8 Mon Sep 17 00:00:00 2001 From: Kunal Arora <55632507+aroralanuk@users.noreply.github.com> Date: Fri, 13 Dec 2024 13:42:33 +0530 Subject: [PATCH 16/18] fix(contracts): make `releaseValueToRecipient` internal (#4989) ### Description - partially fixes https://github.com/chainlight-io/2024-08-hyperlane/issues/1 ### Drive-by changes ### Related issues ### Backward compatibility ### Testing --- .changeset/nine-eyes-smile.md | 5 +++ .../hook/AbstractMessageIdAuthorizedIsm.sol | 32 +++++++++---------- solidity/contracts/isms/hook/ArbL2ToL1Ism.sol | 2 +- solidity/contracts/isms/hook/OPL2ToL1Ism.sol | 2 +- 4 files changed, 23 insertions(+), 18 deletions(-) create mode 100644 .changeset/nine-eyes-smile.md diff --git a/.changeset/nine-eyes-smile.md b/.changeset/nine-eyes-smile.md new file mode 100644 index 0000000000..e0ae7027b5 --- /dev/null +++ b/.changeset/nine-eyes-smile.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/core': minor +--- + +Made releaseValueToRecipient internal diff --git a/solidity/contracts/isms/hook/AbstractMessageIdAuthorizedIsm.sol b/solidity/contracts/isms/hook/AbstractMessageIdAuthorizedIsm.sol index 51a896129b..a133de5d8d 100644 --- a/solidity/contracts/isms/hook/AbstractMessageIdAuthorizedIsm.sol +++ b/solidity/contracts/isms/hook/AbstractMessageIdAuthorizedIsm.sol @@ -78,28 +78,13 @@ abstract contract AbstractMessageIdAuthorizedIsm is ) external virtual returns (bool) { bool verified = isVerified(message); if (verified) { - releaseValueToRecipient(message); + _releaseValueToRecipient(message); } return verified; } // ============ Public Functions ============ - /** - * @notice Release the value to the recipient if the message is verified. - * @param message Message to release value for. - */ - function releaseValueToRecipient(bytes calldata message) public { - bytes32 messageId = message.id(); - uint256 _msgValue = verifiedMessages[messageId].clearBit( - VERIFIED_MASK_INDEX - ); - if (_msgValue > 0) { - verifiedMessages[messageId] -= _msgValue; - payable(message.recipientAddress()).sendValue(_msgValue); - } - } - /** * @notice Check if a message is verified through preVerifyMessage first. * @param message Message to check. @@ -138,6 +123,21 @@ abstract contract AbstractMessageIdAuthorizedIsm is // ============ Internal Functions ============ + /** + * @notice Release the value to the recipient if the message is verified. + * @param message Message to release value for. + */ + function _releaseValueToRecipient(bytes calldata message) internal { + bytes32 messageId = message.id(); + uint256 _msgValue = verifiedMessages[messageId].clearBit( + VERIFIED_MASK_INDEX + ); + if (_msgValue > 0) { + verifiedMessages[messageId] -= _msgValue; + payable(message.recipientAddress()).sendValue(_msgValue); + } + } + /** * @notice Check if sender is authorized to message `preVerifyMessage`. */ diff --git a/solidity/contracts/isms/hook/ArbL2ToL1Ism.sol b/solidity/contracts/isms/hook/ArbL2ToL1Ism.sol index 42593f85c6..af7ab6ecd7 100644 --- a/solidity/contracts/isms/hook/ArbL2ToL1Ism.sol +++ b/solidity/contracts/isms/hook/ArbL2ToL1Ism.sol @@ -69,7 +69,7 @@ contract ArbL2ToL1Ism is _verifyWithOutboxCall(metadata, message); require(isVerified(message), "ArbL2ToL1Ism: message not verified"); } - releaseValueToRecipient(message); + _releaseValueToRecipient(message); return true; } diff --git a/solidity/contracts/isms/hook/OPL2ToL1Ism.sol b/solidity/contracts/isms/hook/OPL2ToL1Ism.sol index ef39868611..22b432fab2 100644 --- a/solidity/contracts/isms/hook/OPL2ToL1Ism.sol +++ b/solidity/contracts/isms/hook/OPL2ToL1Ism.sol @@ -70,7 +70,7 @@ contract OPL2ToL1Ism is _verifyWithPortalCall(metadata, message); require(isVerified(message), "OPL2ToL1Ism: message not verified"); } - releaseValueToRecipient(message); + _releaseValueToRecipient(message); return true; } From adaff79d66b0b074b93e264ee2a03d3867c6e3d8 Mon Sep 17 00:00:00 2001 From: Mohammed Hussan Date: Fri, 13 Dec 2024 11:13:54 +0000 Subject: [PATCH 17/18] feat(warpRoutes): Add MAGIC/arbitrum-treasure to WarpRouteIds (#4998) ### Description Add MAGIC/arbitrum-treasure to WarpRouteIds --- typescript/infra/config/environments/mainnet3/warp/warpIds.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/typescript/infra/config/environments/mainnet3/warp/warpIds.ts b/typescript/infra/config/environments/mainnet3/warp/warpIds.ts index 685567e4ad..34b767c314 100644 --- a/typescript/infra/config/environments/mainnet3/warp/warpIds.ts +++ b/typescript/infra/config/environments/mainnet3/warp/warpIds.ts @@ -5,6 +5,7 @@ export enum WarpRouteIds { ArbitrumEthereumZircuitAMPHRETH = 'AMPHRETH/arbitrum-ethereum-zircuit', ArbitrumNeutronEclip = 'ECLIP/arbitrum-neutron', ArbitrumNeutronTIA = 'TIA/arbitrum-neutron', + ArbitrumTreasureMAGIC = 'MAGIC/arbitrum-treasure', EclipseEthereumApxEth = 'APXETH/eclipsemainnet-ethereum', EclipseEthereumSolanaUSDC = 'USDC/eclipsemainnet-ethereum-solanamainnet', EclipseEthereumSolanaUSDT = 'USDT/eclipsemainnet-ethereum-solanamainnet', From ea75978f8cf84d4dcc19e8c354b76ee29fa7e201 Mon Sep 17 00:00:00 2001 From: Daniel Savu <23065004+daniel-savu@users.noreply.github.com> Date: Fri, 13 Dec 2024 11:35:01 +0000 Subject: [PATCH 18/18] fix(treasure): gas price estimation (#4992) ### Description ### Drive-by changes ### Related issues ### Backward compatibility ### Testing --- rust/main/chains/hyperlane-ethereum/src/tx.rs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/rust/main/chains/hyperlane-ethereum/src/tx.rs b/rust/main/chains/hyperlane-ethereum/src/tx.rs index e989be11d6..1a95c5436b 100644 --- a/rust/main/chains/hyperlane-ethereum/src/tx.rs +++ b/rust/main/chains/hyperlane-ethereum/src/tx.rs @@ -1,5 +1,5 @@ -use std::sync::Arc; use std::time::Duration; +use std::{ops::Mul, sync::Arc}; use ethers::{ abi::Detokenize, @@ -154,7 +154,7 @@ where } let Ok((base_fee, max_fee, max_priority_fee)) = - estimate_eip1559_fees(provider, None, &latest_block).await + estimate_eip1559_fees(provider, None, &latest_block, domain).await else { // Is not EIP 1559 chain return Ok(tx.gas(gas_limit)); @@ -210,6 +210,7 @@ async fn estimate_eip1559_fees( provider: Arc, estimator: Option, latest_block: &Block, + domain: &HyperlaneDomain, ) -> ChainResult<(EthersU256, EthersU256, EthersU256)> where M: Middleware + 'static, @@ -234,7 +235,17 @@ where eip1559_default_estimator(base_fee_per_gas, fee_history.reward) }; - Ok((base_fee_per_gas, max_fee_per_gas, max_priority_fee_per_gas)) + let mut gas_price_multiplier: u32 = 1; + // `treasure` chain gas estimation underestimates the actual gas price, so we add a multiplier to it + if domain.id() == 61166 { + gas_price_multiplier = 2; + } + + Ok(( + base_fee_per_gas.mul(gas_price_multiplier), + max_fee_per_gas.mul(gas_price_multiplier), + max_priority_fee_per_gas.mul(gas_price_multiplier), + )) } pub(crate) async fn call_with_reorg_period(