From cd7a263eb6eb7bfb3b771c16cba94c3351640790 Mon Sep 17 00:00:00 2001 From: Lee <6251863+ltyu@users.noreply.github.com> Date: Tue, 22 Oct 2024 17:41:08 -0400 Subject: [PATCH 01/61] fix: revert concurrency in Modules (#4730) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Description Reverts https://github.com/hyperlane-xyz/hyperlane-monorepo/pull/4720 Reasons: - Many of the public RPCs don't allow concurrency reads. The ones that do don't tell us how much they allow so no point in defaulting to some assumed number (20) - retryAsync doesn't do anything here as the SmartProvider knows what to do if a provider fails (retries with cooldown). SmartProvider has it's own retryAsync - Concurrency is configurable in the metadata. so configure for each provider, as intended. Screenshot 2024-10-22 at 4 11 02 PM --- .changeset/fresh-donkeys-smile.md | 6 -- typescript/cli/src/utils/files.ts | 7 +- typescript/sdk/src/consts/concurrency.ts | 2 +- typescript/sdk/src/hook/EvmHookReader.ts | 127 +++++++++++------------ typescript/sdk/src/ism/EvmIsmReader.ts | 97 +++++++++-------- 5 files changed, 113 insertions(+), 126 deletions(-) delete mode 100644 .changeset/fresh-donkeys-smile.md diff --git a/.changeset/fresh-donkeys-smile.md b/.changeset/fresh-donkeys-smile.md deleted file mode 100644 index 23339249a4..0000000000 --- a/.changeset/fresh-donkeys-smile.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@hyperlane-xyz/cli': minor -'@hyperlane-xyz/sdk': minor ---- - -Enable concurrency for IsmModule diff --git a/typescript/cli/src/utils/files.ts b/typescript/cli/src/utils/files.ts index 17b115a989..9c7cb6216e 100644 --- a/typescript/cli/src/utils/files.ts +++ b/typescript/cli/src/utils/files.ts @@ -15,7 +15,6 @@ import { objMerge } from '@hyperlane-xyz/utils'; import { log } from '../logger.js'; export const MAX_READ_LINE_OUTPUT = 250; -export const MAX_ALIAS_YAML = 100_000; // Used for yaml maxAliasCount. Ref: https://eemeli.org/yaml/#tojs-options export type FileFormat = 'yaml' | 'json'; @@ -93,9 +92,7 @@ export function mergeJson>( } export function readYaml(filepath: string): T { - return yamlParse(readFileAtPath(filepath), { - maxAliasCount: MAX_ALIAS_YAML, - }) as T; + return yamlParse(readFileAtPath(filepath)) as T; } export function tryReadYamlAtPath(filepath: string): T | null { @@ -253,7 +250,7 @@ export function logYamlIfUnderMaxLines( ): void { const asYamlString = yamlStringify(obj, null, margin); const lineCounter = new LineCounter(); - parse(asYamlString, { lineCounter, maxAliasCount: MAX_ALIAS_YAML }); + parse(asYamlString, { lineCounter }); log(lineCounter.lineStarts.length < maxLines ? asYamlString : ''); } diff --git a/typescript/sdk/src/consts/concurrency.ts b/typescript/sdk/src/consts/concurrency.ts index 62e6c801cc..d9cc8fa7c6 100644 --- a/typescript/sdk/src/consts/concurrency.ts +++ b/typescript/sdk/src/consts/concurrency.ts @@ -1 +1 @@ -export const DEFAULT_CONTRACT_READ_CONCURRENCY = 20; +export const DEFAULT_CONTRACT_READ_CONCURRENCY = 1; diff --git a/typescript/sdk/src/hook/EvmHookReader.ts b/typescript/sdk/src/hook/EvmHookReader.ts index a98e72b1ba..518b63e585 100644 --- a/typescript/sdk/src/hook/EvmHookReader.ts +++ b/typescript/sdk/src/hook/EvmHookReader.ts @@ -22,7 +22,6 @@ import { concurrentMap, eqAddress, getLogLevel, - retryAsync, rootLogger, } from '@hyperlane-xyz/utils'; @@ -116,72 +115,72 @@ export class EvmHookReader extends HyperlaneReader implements HookReader { `Cache miss for HookConfig on chain ${this.chain} at: ${address}`, ); - return retryAsync(async () => { - let onchainHookType: OnchainHookType | undefined = undefined; - let derivedHookConfig: DerivedHookConfig; - try { - const hook = IPostDispatchHook__factory.connect(address, this.provider); - - // Temporarily turn off SmartProvider logging - // Provider errors are expected because deriving will call methods that may not exist in the Bytecode - this.setSmartProviderLogLevel('silent'); - onchainHookType = await hook.hookType(); - - switch (onchainHookType) { - case OnchainHookType.ROUTING: - derivedHookConfig = await this.deriveDomainRoutingConfig(address); - break; - case OnchainHookType.AGGREGATION: - derivedHookConfig = await this.deriveAggregationConfig(address); - break; - case OnchainHookType.MERKLE_TREE: - derivedHookConfig = await this.deriveMerkleTreeConfig(address); - break; - case OnchainHookType.INTERCHAIN_GAS_PAYMASTER: - derivedHookConfig = await this.deriveIgpConfig(address); - break; - case OnchainHookType.FALLBACK_ROUTING: - derivedHookConfig = await this.deriveFallbackRoutingConfig(address); - break; - case OnchainHookType.PAUSABLE: - derivedHookConfig = await this.derivePausableConfig(address); - break; - case OnchainHookType.PROTOCOL_FEE: - derivedHookConfig = await this.deriveProtocolFeeConfig(address); - break; - // ID_AUTH_ISM could be OPStackHook, ERC5164Hook or LayerZeroV2Hook - // For now assume it's OP_STACK - case OnchainHookType.ID_AUTH_ISM: - derivedHookConfig = await this.deriveOpStackConfig(address); - break; - case OnchainHookType.ARB_L2_TO_L1: - derivedHookConfig = await this.deriveArbL2ToL1Config(address); - break; - default: - throw new Error( - `Unsupported HookType: ${OnchainHookType[onchainHookType]}`, - ); - } - } catch (e: any) { - let customMessage: string = `Failed to derive ${onchainHookType} hook (${address})`; - if ( - !onchainHookType && - e.message.includes('Invalid response from provider') - ) { - customMessage = customMessage.concat( - ` [The provided hook contract might be outdated and not support hookType()]`, + let onchainHookType: OnchainHookType | undefined = undefined; + let derivedHookConfig: DerivedHookConfig; + + try { + const hook = IPostDispatchHook__factory.connect(address, this.provider); + this.logger.debug('Deriving HookConfig:', { address }); + + // Temporarily turn off SmartProvider logging + // Provider errors are expected because deriving will call methods that may not exist in the Bytecode + this.setSmartProviderLogLevel('silent'); + onchainHookType = await hook.hookType(); + + switch (onchainHookType) { + case OnchainHookType.ROUTING: + derivedHookConfig = await this.deriveDomainRoutingConfig(address); + break; + case OnchainHookType.AGGREGATION: + derivedHookConfig = await this.deriveAggregationConfig(address); + break; + case OnchainHookType.MERKLE_TREE: + derivedHookConfig = await this.deriveMerkleTreeConfig(address); + break; + case OnchainHookType.INTERCHAIN_GAS_PAYMASTER: + derivedHookConfig = await this.deriveIgpConfig(address); + break; + case OnchainHookType.FALLBACK_ROUTING: + derivedHookConfig = await this.deriveFallbackRoutingConfig(address); + break; + case OnchainHookType.PAUSABLE: + derivedHookConfig = await this.derivePausableConfig(address); + break; + case OnchainHookType.PROTOCOL_FEE: + derivedHookConfig = await this.deriveProtocolFeeConfig(address); + break; + // ID_AUTH_ISM could be OPStackHook, ERC5164Hook or LayerZeroV2Hook + // For now assume it's OP_STACK + case OnchainHookType.ID_AUTH_ISM: + derivedHookConfig = await this.deriveOpStackConfig(address); + break; + case OnchainHookType.ARB_L2_TO_L1: + derivedHookConfig = await this.deriveArbL2ToL1Config(address); + break; + default: + throw new Error( + `Unsupported HookType: ${OnchainHookType[onchainHookType]}`, ); - this.logger.info(`${customMessage}:\n\t${e}`); - } else { - this.logger.debug(`${customMessage}:\n\t${e}`); - } - throw new Error(`${customMessage}:\n\t${e}`); - } finally { - this.setSmartProviderLogLevel(getLogLevel()); // returns to original level defined by rootLogger } + } catch (e: any) { + let customMessage: string = `Failed to derive ${onchainHookType} hook (${address})`; + if ( + !onchainHookType && + e.message.includes('Invalid response from provider') + ) { + customMessage = customMessage.concat( + ` [The provided hook contract might be outdated and not support hookType()]`, + ); + this.logger.info(`${customMessage}:\n\t${e}`); + } else { + this.logger.debug(`${customMessage}:\n\t${e}`); + } + throw new Error(`${customMessage}:\n\t${e}`); + } finally { + this.setSmartProviderLogLevel(getLogLevel()); // returns to original level defined by rootLogger + } - return derivedHookConfig; - }); + return derivedHookConfig; } async deriveMerkleTreeConfig( diff --git a/typescript/sdk/src/ism/EvmIsmReader.ts b/typescript/sdk/src/ism/EvmIsmReader.ts index 6448cede41..c7d766e8ca 100644 --- a/typescript/sdk/src/ism/EvmIsmReader.ts +++ b/typescript/sdk/src/ism/EvmIsmReader.ts @@ -17,7 +17,6 @@ import { assert, concurrentMap, getLogLevel, - retryAsync, rootLogger, } from '@hyperlane-xyz/utils'; @@ -72,57 +71,55 @@ export class EvmIsmReader extends HyperlaneReader implements IsmReader { } async deriveIsmConfig(address: Address): Promise { - return retryAsync(async () => { - let moduleType: ModuleType | undefined = undefined; - let derivedIsmConfig: DerivedIsmConfig; - try { - const ism = IInterchainSecurityModule__factory.connect( - address, - this.provider, - ); - this.logger.debug('Deriving IsmConfig:', { address }); - - // Temporarily turn off SmartProvider logging - // Provider errors are expected because deriving will call methods that may not exist in the Bytecode - this.setSmartProviderLogLevel('silent'); - moduleType = await ism.moduleType(); - - switch (moduleType) { - case ModuleType.UNUSED: - throw new Error('UNUSED does not have a corresponding IsmType'); - case ModuleType.ROUTING: - // IsmType is either ROUTING or FALLBACK_ROUTING, but that's determined inside deriveRoutingConfig - derivedIsmConfig = await this.deriveRoutingConfig(address); - break; - case ModuleType.AGGREGATION: - derivedIsmConfig = await this.deriveAggregationConfig(address); - break; - case ModuleType.LEGACY_MULTISIG: - throw new Error('LEGACY_MULTISIG is deprecated and not supported'); - case ModuleType.MERKLE_ROOT_MULTISIG: - case ModuleType.MESSAGE_ID_MULTISIG: - derivedIsmConfig = await this.deriveMultisigConfig(address); - break; - case ModuleType.NULL: - derivedIsmConfig = await this.deriveNullConfig(address); - break; - case ModuleType.CCIP_READ: - throw new Error('CCIP_READ does not have a corresponding IsmType'); - case ModuleType.ARB_L2_TO_L1: - return this.deriveArbL2ToL1Config(address); - default: - throw new Error(`Unknown ISM ModuleType: ${moduleType}`); - } - } catch (e: any) { - const errorMessage = `Failed to derive ISM module type ${moduleType} on ${this.chain} (${address}) :\n\t${e}`; - this.logger.debug(errorMessage); - throw new Error(errorMessage); - } finally { - this.setSmartProviderLogLevel(getLogLevel()); // returns to original level defined by rootLogger + let moduleType: ModuleType | undefined = undefined; + let derivedIsmConfig: DerivedIsmConfig; + try { + const ism = IInterchainSecurityModule__factory.connect( + address, + this.provider, + ); + this.logger.debug('Deriving IsmConfig:', { address }); + + // Temporarily turn off SmartProvider logging + // Provider errors are expected because deriving will call methods that may not exist in the Bytecode + this.setSmartProviderLogLevel('silent'); + moduleType = await ism.moduleType(); + + switch (moduleType) { + case ModuleType.UNUSED: + throw new Error('UNUSED does not have a corresponding IsmType'); + case ModuleType.ROUTING: + // IsmType is either ROUTING or FALLBACK_ROUTING, but that's determined inside deriveRoutingConfig + derivedIsmConfig = await this.deriveRoutingConfig(address); + break; + case ModuleType.AGGREGATION: + derivedIsmConfig = await this.deriveAggregationConfig(address); + break; + case ModuleType.LEGACY_MULTISIG: + throw new Error('LEGACY_MULTISIG is deprecated and not supported'); + case ModuleType.MERKLE_ROOT_MULTISIG: + case ModuleType.MESSAGE_ID_MULTISIG: + derivedIsmConfig = await this.deriveMultisigConfig(address); + break; + case ModuleType.NULL: + derivedIsmConfig = await this.deriveNullConfig(address); + break; + case ModuleType.CCIP_READ: + throw new Error('CCIP_READ does not have a corresponding IsmType'); + case ModuleType.ARB_L2_TO_L1: + return this.deriveArbL2ToL1Config(address); + default: + throw new Error(`Unknown ISM ModuleType: ${moduleType}`); } + } catch (e: any) { + const errorMessage = `Failed to derive ISM module type ${moduleType} on ${this.chain} (${address}) :\n\t${e}`; + this.logger.debug(errorMessage); + throw new Error(errorMessage); + } finally { + this.setSmartProviderLogLevel(getLogLevel()); // returns to original level defined by rootLogger + } - return derivedIsmConfig; - }); + return derivedIsmConfig; } async deriveRoutingConfig( From c3e9268f1e2a4f69bd430e85a889a128957ca9fe Mon Sep 17 00:00:00 2001 From: Alexander Pastushenka Date: Wed, 23 Oct 2024 15:09:56 +0400 Subject: [PATCH 02/61] feat: Add block tag support in `reorgPeriod` parameter (#4629) ### Description Allows setting an arbitrary string in `reorgPeriod`, which is used as a block tag to get the finalized block. Currently, only the Ethereum connector supports this feature with the following tags: - finalized - safe - pending - latest - earliest This is currently useful for EVM-compatible chains that don't have a fixed reorganization period, but instead rely on the block tag `Finalized` to indicate finality. For such chains, you should set `reorgPeriod` to `finalized` instead of a number of blocks. ### Drive-by changes ### Related issues ### Backward compatibility Yes ### Testing --------- Co-authored-by: Daniel Savu <23065004+daniel-savu@users.noreply.github.com> --- .changeset/strange-beers-buy.md | 7 ++ rust/main/agents/validator/src/settings.rs | 12 +- rust/main/agents/validator/src/submit.rs | 26 ++-- rust/main/agents/validator/src/validator.rs | 17 ++- .../hyperlane-cosmos/src/mailbox/contract.rs | 9 +- .../hyperlane-cosmos/src/merkle_tree_hook.rs | 22 ++-- .../main/chains/hyperlane-cosmos/src/utils.rs | 25 ++-- .../chains/hyperlane-ethereum/src/config.rs | 34 +++++- .../src/contracts/interchain_gas.rs | 28 ++--- .../src/contracts/mailbox.rs | 37 +++--- .../src/contracts/merkle_tree_hook.rs | 51 ++++---- .../hyperlane-ethereum/src/contracts/mod.rs | 2 + .../hyperlane-ethereum/src/contracts/utils.rs | 35 +++++- rust/main/chains/hyperlane-ethereum/src/tx.rs | 24 ++-- .../main/chains/hyperlane-fuel/src/mailbox.rs | 9 +- .../chains/hyperlane-sealevel/src/mailbox.rs | 10 +- .../src/merkle_tree_hook.rs | 18 +-- .../hyperlane-base/src/settings/chains.rs | 42 ++++--- .../src/settings/checkpoint_syncer.rs | 8 +- .../hyperlane-base/src/settings/parser/mod.rs | 6 +- rust/main/hyperlane-core/src/chain.rs | 114 +++++++++++++++++- rust/main/hyperlane-core/src/error.rs | 9 +- .../main/hyperlane-core/src/traits/mailbox.rs | 7 +- .../src/traits/merkle_tree_hook.rs | 14 +-- rust/main/hyperlane-core/src/types/reorg.rs | 6 +- rust/main/hyperlane-test/src/mocks/mailbox.rs | 12 +- typescript/cli/src/config/chain.ts | 13 +- typescript/infra/config/registry.ts | 2 +- .../infra/src/config/agent/validator.ts | 4 +- .../sdk/src/metadata/chainMetadata.test.ts | 10 ++ .../sdk/src/metadata/chainMetadataTypes.ts | 11 +- 31 files changed, 421 insertions(+), 203 deletions(-) create mode 100644 .changeset/strange-beers-buy.md diff --git a/.changeset/strange-beers-buy.md b/.changeset/strange-beers-buy.md new file mode 100644 index 0000000000..3ad5bc57ec --- /dev/null +++ b/.changeset/strange-beers-buy.md @@ -0,0 +1,7 @@ +--- +'@hyperlane-xyz/infra': minor +'@hyperlane-xyz/cli': minor +'@hyperlane-xyz/sdk': minor +--- + +Add support for an arbitrary string in `reorgPeriod`, which is used as a block tag to get the finalized block. diff --git a/rust/main/agents/validator/src/settings.rs b/rust/main/agents/validator/src/settings.rs index 02dc87cb13..d464be5a92 100644 --- a/rust/main/agents/validator/src/settings.rs +++ b/rust/main/agents/validator/src/settings.rs @@ -15,7 +15,9 @@ use hyperlane_base::{ CheckpointSyncerConf, Settings, SignerConf, }, }; -use hyperlane_core::{cfg_unwrap_all, config::*, HyperlaneDomain, HyperlaneDomainProtocol}; +use hyperlane_core::{ + cfg_unwrap_all, config::*, HyperlaneDomain, HyperlaneDomainProtocol, ReorgPeriod, +}; use serde::Deserialize; use serde_json::Value; @@ -36,8 +38,8 @@ pub struct ValidatorSettings { pub validator: SignerConf, /// The checkpoint syncer configuration pub checkpoint_syncer: CheckpointSyncerConf, - /// The reorg_period in blocks - pub reorg_period: u64, + /// The reorg configuration + pub reorg_period: ReorgPeriod, /// How frequently to check for new checkpoints pub interval: Duration, } @@ -122,8 +124,8 @@ impl FromRawConf for ValidatorSettings { .get_key(origin_chain_name) .get_opt_key("blocks") .get_opt_key("reorgPeriod") - .parse_u64() - .unwrap_or(1); + .parse_value("Invalid reorgPeriod") + .unwrap_or(ReorgPeriod::from_blocks(1)); cfg_unwrap_all!(cwp, err: [base, origin_chain, validator, checkpoint_syncer]); diff --git a/rust/main/agents/validator/src/submit.rs b/rust/main/agents/validator/src/submit.rs index 94d5964673..954b8d0d95 100644 --- a/rust/main/agents/validator/src/submit.rs +++ b/rust/main/agents/validator/src/submit.rs @@ -1,4 +1,3 @@ -use std::num::NonZeroU64; use std::sync::Arc; use std::time::{Duration, Instant}; use std::vec; @@ -14,13 +13,13 @@ use hyperlane_core::{ accumulator::incremental::IncrementalMerkle, Checkpoint, CheckpointWithMessageId, HyperlaneChain, HyperlaneContract, HyperlaneDomain, HyperlaneSignerExt, }; -use hyperlane_core::{ChainResult, MerkleTreeHook, ReorgEvent}; +use hyperlane_core::{ChainResult, MerkleTreeHook, ReorgEvent, ReorgPeriod}; use hyperlane_ethereum::SingletonSignerHandle; #[derive(Clone)] pub(crate) struct ValidatorSubmitter { interval: Duration, - reorg_period: Option, + reorg_period: ReorgPeriod, signer: SingletonSignerHandle, merkle_tree_hook: Arc, checkpoint_syncer: Arc, @@ -31,7 +30,7 @@ pub(crate) struct ValidatorSubmitter { impl ValidatorSubmitter { pub(crate) fn new( interval: Duration, - reorg_period: u64, + reorg_period: ReorgPeriod, merkle_tree_hook: Arc, signer: SingletonSignerHandle, checkpoint_syncer: Arc, @@ -39,7 +38,7 @@ impl ValidatorSubmitter { metrics: ValidatorSubmitterMetrics, ) -> Self { Self { - reorg_period: NonZeroU64::new(reorg_period), + reorg_period, interval, merkle_tree_hook, signer, @@ -94,7 +93,8 @@ impl ValidatorSubmitter { // Lag by reorg period because this is our correctness checkpoint. let latest_checkpoint = call_and_retry_indefinitely(|| { let merkle_tree_hook = self.merkle_tree_hook.clone(); - Box::pin(async move { merkle_tree_hook.latest_checkpoint(self.reorg_period).await }) + let reorg_period = self.reorg_period.clone(); + Box::pin(async move { merkle_tree_hook.latest_checkpoint(&reorg_period).await }) }) .await; @@ -211,7 +211,7 @@ impl ValidatorSubmitter { correctness_checkpoint.root, checkpoint.index, chrono::Utc::now().timestamp() as u64, - self.reorg_period.map(|x| x.get()).unwrap_or(0), + self.reorg_period.clone(), ); error!( ?checkpoint, @@ -486,9 +486,9 @@ mod test { #[async_trait] impl MerkleTreeHook for MerkleTreeHook { - async fn tree(&self, lag: Option) -> ChainResult; - async fn count(&self, lag: Option) -> ChainResult; - async fn latest_checkpoint(&self, lag: Option) -> ChainResult; + async fn tree(&self, reorg_period: &ReorgPeriod) -> ChainResult; + async fn count(&self, reorg_period: &ReorgPeriod) -> ChainResult; + async fn latest_checkpoint(&self, reorg_period: &ReorgPeriod) -> ChainResult; } } @@ -532,7 +532,7 @@ mod test { expected_local_merkle_tree: &IncrementalMerkle, mock_onchain_merkle_tree: &IncrementalMerkle, unix_timestamp: u64, - expected_reorg_period: u64, + expected_reorg_period: ReorgPeriod, ) { assert_eq!( reorg_event.canonical_merkle_root, @@ -617,7 +617,7 @@ mod test { &expected_local_merkle_tree, &mock_onchain_merkle_tree_clone, unix_timestamp, - expected_reorg_period, + ReorgPeriod::from_blocks(expected_reorg_period), ); Ok(()) }); @@ -625,7 +625,7 @@ mod test { // instantiate the validator submitter let validator_submitter = ValidatorSubmitter::new( Duration::from_secs(1), - expected_reorg_period, + ReorgPeriod::from_blocks(expected_reorg_period), Arc::new(mock_merkle_tree_hook), dummy_singleton_handle(), Arc::new(mock_checkpoint_syncer), diff --git a/rust/main/agents/validator/src/validator.rs b/rust/main/agents/validator/src/validator.rs index 31fe809b48..2d09bd93ff 100644 --- a/rust/main/agents/validator/src/validator.rs +++ b/rust/main/agents/validator/src/validator.rs @@ -1,4 +1,4 @@ -use std::{num::NonZeroU64, sync::Arc, time::Duration}; +use std::{sync::Arc, time::Duration}; use crate::server as validator_server; use async_trait::async_trait; @@ -19,8 +19,8 @@ use hyperlane_base::{ use hyperlane_core::{ Announcement, ChainResult, HyperlaneChain, HyperlaneContract, HyperlaneDomain, HyperlaneSigner, - HyperlaneSignerExt, Mailbox, MerkleTreeHook, MerkleTreeInsertion, TxOutcome, ValidatorAnnounce, - H256, U256, + HyperlaneSignerExt, Mailbox, MerkleTreeHook, MerkleTreeInsertion, ReorgPeriod, TxOutcome, + ValidatorAnnounce, H256, U256, }; use hyperlane_ethereum::{SingletonSigner, SingletonSignerHandle}; @@ -44,7 +44,7 @@ pub struct Validator { signer: SingletonSignerHandle, // temporary holder until `run` is called signer_instance: Option>, - reorg_period: u64, + reorg_period: ReorgPeriod, interval: Duration, checkpoint_syncer: Arc, core_metrics: Arc, @@ -184,12 +184,10 @@ impl BaseAgent for Validator { // announce the validator after spawning the signer task self.announce().await.expect("Failed to announce validator"); - let reorg_period = NonZeroU64::new(self.reorg_period); - // Ensure that the merkle tree hook has count > 0 before we begin indexing // messages or submitting checkpoints. loop { - match self.merkle_tree_hook.count(reorg_period).await { + match self.merkle_tree_hook.count(&self.reorg_period).await { Ok(0) => { info!("Waiting for first message in merkle tree hook"); sleep(self.interval).await; @@ -241,7 +239,7 @@ impl Validator { async fn run_checkpoint_submitters(&self) -> Vec>> { let submitter = ValidatorSubmitter::new( self.interval, - self.reorg_period, + self.reorg_period.clone(), self.merkle_tree_hook.clone(), self.signer.clone(), self.checkpoint_syncer.clone(), @@ -249,10 +247,9 @@ impl Validator { ValidatorSubmitterMetrics::new(&self.core.metrics, &self.origin_chain), ); - let reorg_period = NonZeroU64::new(self.reorg_period); let tip_tree = self .merkle_tree_hook - .tree(reorg_period) + .tree(&self.reorg_period) .await .expect("failed to get merkle tree"); // This function is only called after we have already checked that the diff --git a/rust/main/chains/hyperlane-cosmos/src/mailbox/contract.rs b/rust/main/chains/hyperlane-cosmos/src/mailbox/contract.rs index 9c793c93e4..5a998aac5a 100644 --- a/rust/main/chains/hyperlane-cosmos/src/mailbox/contract.rs +++ b/rust/main/chains/hyperlane-cosmos/src/mailbox/contract.rs @@ -8,7 +8,7 @@ use tracing::instrument; use hyperlane_core::{ utils::bytes_to_hex, ChainResult, ContractLocator, HyperlaneChain, HyperlaneContract, HyperlaneDomain, HyperlaneMessage, HyperlaneProvider, Mailbox, RawHyperlaneMessage, - TxCostEstimate, TxOutcome, H256, U256, + ReorgPeriod, TxCostEstimate, TxOutcome, H256, U256, }; use crate::grpc::WasmProvider; @@ -17,7 +17,7 @@ use crate::payloads::mailbox::{ GeneralMailboxQuery, ProcessMessageRequest, ProcessMessageRequestInner, }; use crate::types::tx_response_to_outcome; -use crate::utils::get_block_height_for_lag; +use crate::utils::get_block_height_for_reorg_period; use crate::{payloads, ConnectionConf, CosmosAddress, CosmosProvider, Signer}; #[derive(Clone, Debug)] @@ -82,8 +82,9 @@ impl HyperlaneChain for CosmosMailbox { impl Mailbox for CosmosMailbox { #[instrument(level = "debug", err, ret, skip(self))] #[allow(clippy::blocks_in_conditions)] // TODO: `rustc` 1.80.1 clippy issue - async fn count(&self, lag: Option) -> ChainResult { - let block_height = get_block_height_for_lag(self.provider.grpc(), lag).await?; + async fn count(&self, reorg_period: &ReorgPeriod) -> ChainResult { + let block_height = + get_block_height_for_reorg_period(self.provider.grpc(), reorg_period).await?; self.nonce_at_block(block_height).await } diff --git a/rust/main/chains/hyperlane-cosmos/src/merkle_tree_hook.rs b/rust/main/chains/hyperlane-cosmos/src/merkle_tree_hook.rs index c9e48c59f2..b9acdd3571 100644 --- a/rust/main/chains/hyperlane-cosmos/src/merkle_tree_hook.rs +++ b/rust/main/chains/hyperlane-cosmos/src/merkle_tree_hook.rs @@ -10,15 +10,15 @@ use hyperlane_core::accumulator::incremental::IncrementalMerkle; use hyperlane_core::{ ChainCommunicationError, ChainResult, Checkpoint, ContractLocator, HyperlaneChain, HyperlaneContract, HyperlaneDomain, HyperlaneProvider, Indexed, Indexer, LogMeta, - MerkleTreeHook, MerkleTreeInsertion, SequenceAwareIndexer, H256, H512, + MerkleTreeHook, MerkleTreeInsertion, ReorgPeriod, SequenceAwareIndexer, H256, H512, }; use crate::grpc::WasmProvider; use crate::payloads::{general, merkle_tree_hook}; use crate::rpc::{CosmosWasmRpcProvider, ParsedEvent, WasmRpcProvider}; use crate::utils::{ - execute_and_parse_log_futures, get_block_height_for_lag, parse_logs_in_range, parse_logs_in_tx, - CONTRACT_ADDRESS_ATTRIBUTE_KEY, CONTRACT_ADDRESS_ATTRIBUTE_KEY_BASE64, + execute_and_parse_log_futures, get_block_height_for_reorg_period, parse_logs_in_range, + parse_logs_in_tx, CONTRACT_ADDRESS_ATTRIBUTE_KEY, CONTRACT_ADDRESS_ATTRIBUTE_KEY_BASE64, }; use crate::{ConnectionConf, CosmosProvider, HyperlaneCosmosError, Signer}; @@ -76,12 +76,13 @@ impl MerkleTreeHook for CosmosMerkleTreeHook { /// Return the incremental merkle tree in storage #[instrument(level = "debug", err, ret, skip(self))] #[allow(clippy::blocks_in_conditions)] // TODO: `rustc` 1.80.1 clippy issue - async fn tree(&self, lag: Option) -> ChainResult { + async fn tree(&self, reorg_period: &ReorgPeriod) -> ChainResult { let payload = merkle_tree_hook::MerkleTreeRequest { tree: general::EmptyStruct {}, }; - let block_height = get_block_height_for_lag(self.provider.grpc(), lag).await?; + let block_height = + get_block_height_for_reorg_period(self.provider.grpc(), reorg_period).await?; let data = self .provider @@ -110,23 +111,26 @@ impl MerkleTreeHook for CosmosMerkleTreeHook { } /// Gets the current leaf count of the merkle tree - async fn count(&self, lag: Option) -> ChainResult { + async fn count(&self, reorg_period: &ReorgPeriod) -> ChainResult { let payload = merkle_tree_hook::MerkleTreeCountRequest { count: general::EmptyStruct {}, }; - let block_height = get_block_height_for_lag(self.provider.grpc(), lag).await?; + let block_height = + get_block_height_for_reorg_period(self.provider.grpc(), reorg_period).await?; self.count_at_block(block_height).await } + #[instrument(level = "debug", err, ret, skip(self))] #[allow(clippy::blocks_in_conditions)] // TODO: `rustc` 1.80.1 clippy issue - async fn latest_checkpoint(&self, lag: Option) -> ChainResult { + async fn latest_checkpoint(&self, reorg_period: &ReorgPeriod) -> ChainResult { let payload = merkle_tree_hook::CheckPointRequest { check_point: general::EmptyStruct {}, }; - let block_height = get_block_height_for_lag(self.provider.grpc(), lag).await?; + let block_height = + get_block_height_for_reorg_period(self.provider.grpc(), reorg_period).await?; let data = self .provider diff --git a/rust/main/chains/hyperlane-cosmos/src/utils.rs b/rust/main/chains/hyperlane-cosmos/src/utils.rs index 74cb75a279..f792090e67 100644 --- a/rust/main/chains/hyperlane-cosmos/src/utils.rs +++ b/rust/main/chains/hyperlane-cosmos/src/utils.rs @@ -11,7 +11,7 @@ use tendermint::Hash; use tokio::task::JoinHandle; use tracing::warn; -use hyperlane_core::{ChainCommunicationError, ChainResult, Indexed, LogMeta, H256}; +use hyperlane_core::{ChainCommunicationError, ChainResult, Indexed, LogMeta, ReorgPeriod, H256}; use crate::grpc::{WasmGrpcProvider, WasmProvider}; use crate::rpc::{CosmosWasmRpcProvider, ParsedEvent, WasmRpcProvider}; @@ -24,20 +24,25 @@ pub(crate) const CONTRACT_ADDRESS_ATTRIBUTE_KEY: &str = "_contract_address"; pub(crate) static CONTRACT_ADDRESS_ATTRIBUTE_KEY_BASE64: Lazy = Lazy::new(|| BASE64.encode(CONTRACT_ADDRESS_ATTRIBUTE_KEY)); -/// Given a lag, returns the block height at the moment. -/// If the lag is None, a block height of None is given, indicating that the -/// tip directly can be used. -pub(crate) async fn get_block_height_for_lag( +/// Given a `reorg_period`, returns the block height at the moment. +/// If the `reorg_period` is None, a block height of None is given, +/// indicating that the tip directly can be used. +pub(crate) async fn get_block_height_for_reorg_period( provider: &WasmGrpcProvider, - lag: Option, + reorg_period: &ReorgPeriod, ) -> ChainResult> { - let block_height = match lag { - Some(lag) => { + let block_height = match reorg_period { + ReorgPeriod::Blocks(blocks) => { let tip = provider.latest_block_height().await?; - let block_height = tip - lag.get(); + let block_height = tip - blocks.get() as u64; Some(block_height) } - None => None, + ReorgPeriod::None => None, + ReorgPeriod::Tag(_) => { + return Err(ChainCommunicationError::InvalidReorgPeriod( + reorg_period.clone(), + )) + } }; Ok(block_height) diff --git a/rust/main/chains/hyperlane-ethereum/src/config.rs b/rust/main/chains/hyperlane-ethereum/src/config.rs index 8735b5fd25..0e2a2a13aa 100644 --- a/rust/main/chains/hyperlane-ethereum/src/config.rs +++ b/rust/main/chains/hyperlane-ethereum/src/config.rs @@ -1,4 +1,5 @@ -use hyperlane_core::{config::OperationBatchConfig, U256}; +use ethers_core::types::{BlockId, BlockNumber}; +use hyperlane_core::{config::OperationBatchConfig, ChainCommunicationError, ReorgPeriod, U256}; use url::Url; /// Ethereum RPC connection configuration @@ -52,3 +53,34 @@ pub struct TransactionOverrides { /// Max priority fee per gas to use for EIP-1559 transactions. pub max_priority_fee_per_gas: Option, } + +/// Ethereum reorg period +#[derive(Copy, Clone, Debug)] +pub enum EthereumReorgPeriod { + /// Number of blocks + Blocks(u32), + /// A block tag + Tag(BlockId), +} + +impl TryFrom<&ReorgPeriod> for EthereumReorgPeriod { + type Error = ChainCommunicationError; + + fn try_from(value: &ReorgPeriod) -> Result { + match value { + ReorgPeriod::None => Ok(EthereumReorgPeriod::Blocks(0)), + ReorgPeriod::Blocks(blocks) => Ok(EthereumReorgPeriod::Blocks(blocks.get())), + ReorgPeriod::Tag(tag) => { + let tag = match tag.as_str() { + "latest" => BlockNumber::Latest, + "finalized" => BlockNumber::Finalized, + "safe" => BlockNumber::Safe, + "earliest" => BlockNumber::Earliest, + "pending" => BlockNumber::Pending, + _ => return Err(ChainCommunicationError::InvalidReorgPeriod(value.clone())), + }; + Ok(EthereumReorgPeriod::Tag(tag.into())) + } + } + } +} diff --git a/rust/main/chains/hyperlane-ethereum/src/contracts/interchain_gas.rs b/rust/main/chains/hyperlane-ethereum/src/contracts/interchain_gas.rs index b14903a640..d7cf37e814 100644 --- a/rust/main/chains/hyperlane-ethereum/src/contracts/interchain_gas.rs +++ b/rust/main/chains/hyperlane-ethereum/src/contracts/interchain_gas.rs @@ -9,18 +9,18 @@ use async_trait::async_trait; use ethers::prelude::Middleware; use hyperlane_core::rpc_clients::call_and_retry_indefinitely; use hyperlane_core::{ - ChainCommunicationError, ChainResult, ContractLocator, HyperlaneAbi, HyperlaneChain, - HyperlaneContract, HyperlaneDomain, HyperlaneProvider, Indexed, Indexer, - InterchainGasPaymaster, InterchainGasPayment, LogMeta, SequenceAwareIndexer, H160, H256, H512, + ChainResult, ContractLocator, HyperlaneAbi, HyperlaneChain, HyperlaneContract, HyperlaneDomain, + HyperlaneProvider, Indexed, Indexer, InterchainGasPaymaster, InterchainGasPayment, LogMeta, + SequenceAwareIndexer, H160, H256, H512, }; use tracing::instrument; -use super::utils::fetch_raw_logs_and_meta; +use super::utils::{fetch_raw_logs_and_meta, get_finalized_block_number}; use crate::interfaces::i_interchain_gas_paymaster::{ GasPaymentFilter, IInterchainGasPaymaster as EthereumInterchainGasPaymasterInternal, IINTERCHAINGASPAYMASTER_ABI, }; -use crate::{BuildableWithProvider, ConnectionConf, EthereumProvider}; +use crate::{BuildableWithProvider, ConnectionConf, EthereumProvider, EthereumReorgPeriod}; impl Display for EthereumInterchainGasPaymasterInternal where @@ -33,7 +33,7 @@ where pub struct InterchainGasPaymasterIndexerBuilder { pub mailbox_address: H160, - pub reorg_period: u32, + pub reorg_period: EthereumReorgPeriod, } #[async_trait] @@ -63,7 +63,7 @@ where { contract: Arc>, provider: Arc, - reorg_period: u32, + reorg_period: EthereumReorgPeriod, } impl EthereumInterchainGasPaymasterIndexer @@ -71,7 +71,11 @@ where M: Middleware + 'static, { /// Create new EthereumInterchainGasPaymasterIndexer - pub fn new(provider: Arc, locator: &ContractLocator, reorg_period: u32) -> Self { + pub fn new( + provider: Arc, + locator: &ContractLocator, + reorg_period: EthereumReorgPeriod, + ) -> Self { Self { contract: Arc::new(EthereumInterchainGasPaymasterInternal::new( locator.address, @@ -122,13 +126,7 @@ where #[instrument(level = "debug", err, ret, skip(self))] #[allow(clippy::blocks_in_conditions)] // TODO: `rustc` 1.80.1 clippy issue async fn get_finalized_block_number(&self) -> ChainResult { - Ok(self - .provider - .get_block_number() - .await - .map_err(ChainCommunicationError::from_other)? - .as_u32() - .saturating_sub(self.reorg_period)) + get_finalized_block_number(&self.provider, &self.reorg_period).await } async fn fetch_logs_by_tx_hash( diff --git a/rust/main/chains/hyperlane-ethereum/src/contracts/mailbox.rs b/rust/main/chains/hyperlane-ethereum/src/contracts/mailbox.rs index 72d01e4d62..a9141e7b7f 100644 --- a/rust/main/chains/hyperlane-ethereum/src/contracts/mailbox.rs +++ b/rust/main/chains/hyperlane-ethereum/src/contracts/mailbox.rs @@ -2,7 +2,6 @@ #![allow(missing_docs)] use std::collections::HashMap; -use std::num::NonZeroU64; use std::ops::RangeInclusive; use std::sync::Arc; @@ -14,7 +13,7 @@ use ethers_contract::builders::ContractCall; use ethers_contract::{Multicall, MulticallResult}; use futures_util::future::join_all; use hyperlane_core::rpc_clients::call_and_retry_indefinitely; -use hyperlane_core::{BatchResult, QueueOperation, H512}; +use hyperlane_core::{BatchResult, QueueOperation, ReorgPeriod, H512}; use itertools::Itertools; use tracing::instrument; @@ -31,11 +30,14 @@ use crate::interfaces::i_mailbox::{ IMailbox as EthereumMailboxInternal, ProcessCall, IMAILBOX_ABI, }; use crate::interfaces::mailbox::DispatchFilter; -use crate::tx::{call_with_lag, fill_tx_gas_params, report_tx}; -use crate::{BuildableWithProvider, ConnectionConf, EthereumProvider, TransactionOverrides}; +use crate::tx::{call_with_reorg_period, fill_tx_gas_params, report_tx}; +use crate::{ + BuildableWithProvider, ConnectionConf, EthereumProvider, EthereumReorgPeriod, + TransactionOverrides, +}; use super::multicall::{self, build_multicall}; -use super::utils::fetch_raw_logs_and_meta; +use super::utils::{fetch_raw_logs_and_meta, get_finalized_block_number}; impl std::fmt::Display for EthereumMailboxInternal where @@ -47,7 +49,7 @@ where } pub struct SequenceIndexerBuilder { - pub reorg_period: u32, + pub reorg_period: EthereumReorgPeriod, } #[async_trait] @@ -70,7 +72,7 @@ impl BuildableWithProvider for SequenceIndexerBuilder { } pub struct DeliveryIndexerBuilder { - pub reorg_period: u32, + pub reorg_period: EthereumReorgPeriod, } #[async_trait] @@ -100,7 +102,7 @@ where { contract: Arc>, provider: Arc, - reorg_period: u32, + reorg_period: EthereumReorgPeriod, } impl EthereumMailboxIndexer @@ -108,7 +110,11 @@ where M: Middleware + 'static, { /// Create new EthereumMailboxIndexer - pub fn new(provider: Arc, locator: &ContractLocator, reorg_period: u32) -> Self { + pub fn new( + provider: Arc, + locator: &ContractLocator, + reorg_period: EthereumReorgPeriod, + ) -> Self { let contract = Arc::new(EthereumMailboxInternal::new( locator.address, provider.clone(), @@ -122,13 +128,7 @@ where #[instrument(level = "debug", err, ret, skip(self))] async fn get_finalized_block_number(&self) -> ChainResult { - Ok(self - .provider - .get_block_number() - .await - .map_err(ChainCommunicationError::from_other)? - .as_u32() - .saturating_sub(self.reorg_period)) + get_finalized_block_number(&self.provider, &self.reorg_period).await } } @@ -460,8 +460,9 @@ where M: Middleware + 'static, { #[instrument(skip(self))] - async fn count(&self, maybe_lag: Option) -> ChainResult { - let call = call_with_lag(self.contract.nonce(), &self.provider, maybe_lag).await?; + async fn count(&self, reorg_period: &ReorgPeriod) -> ChainResult { + let call = + call_with_reorg_period(self.contract.nonce(), &self.provider, reorg_period).await?; let nonce = call.call().await?; Ok(nonce) } diff --git a/rust/main/chains/hyperlane-ethereum/src/contracts/merkle_tree_hook.rs b/rust/main/chains/hyperlane-ethereum/src/contracts/merkle_tree_hook.rs index a2f8a4a745..f098d75cad 100644 --- a/rust/main/chains/hyperlane-ethereum/src/contracts/merkle_tree_hook.rs +++ b/rust/main/chains/hyperlane-ethereum/src/contracts/merkle_tree_hook.rs @@ -1,5 +1,4 @@ #![allow(missing_docs)] -use std::num::NonZeroU64; use std::ops::RangeInclusive; use std::sync::Arc; @@ -10,18 +9,18 @@ use hyperlane_core::rpc_clients::call_and_retry_indefinitely; use tracing::instrument; use hyperlane_core::{ - ChainCommunicationError, ChainResult, Checkpoint, ContractLocator, HyperlaneChain, - HyperlaneContract, HyperlaneDomain, HyperlaneProvider, Indexed, Indexer, LogMeta, - MerkleTreeHook, MerkleTreeInsertion, SequenceAwareIndexer, H256, H512, + ChainResult, Checkpoint, ContractLocator, HyperlaneChain, HyperlaneContract, HyperlaneDomain, + HyperlaneProvider, Indexed, Indexer, LogMeta, MerkleTreeHook, MerkleTreeInsertion, ReorgPeriod, + SequenceAwareIndexer, H256, H512, }; use crate::interfaces::merkle_tree_hook::{ InsertedIntoTreeFilter, MerkleTreeHook as MerkleTreeHookContract, Tree, }; -use crate::tx::call_with_lag; -use crate::{BuildableWithProvider, ConnectionConf, EthereumProvider}; +use crate::tx::call_with_reorg_period; +use crate::{BuildableWithProvider, ConnectionConf, EthereumProvider, EthereumReorgPeriod}; -use super::utils::fetch_raw_logs_and_meta; +use super::utils::{fetch_raw_logs_and_meta, get_finalized_block_number}; // We don't need the reverse of this impl, so it's ok to disable the clippy lint #[allow(clippy::from_over_into)] @@ -58,7 +57,7 @@ impl BuildableWithProvider for MerkleTreeHookBuilder { } pub struct MerkleTreeHookIndexerBuilder { - pub reorg_period: u32, + pub reorg_period: EthereumReorgPeriod, } #[async_trait] @@ -88,7 +87,7 @@ where { contract: Arc>, provider: Arc, - reorg_period: u32, + reorg_period: EthereumReorgPeriod, } impl EthereumMerkleTreeHookIndexer @@ -96,7 +95,11 @@ where M: Middleware + 'static, { /// Create new EthereumMerkleTreeHookIndexer - pub fn new(provider: Arc, locator: &ContractLocator, reorg_period: u32) -> Self { + pub fn new( + provider: Arc, + locator: &ContractLocator, + reorg_period: EthereumReorgPeriod, + ) -> Self { Self { contract: Arc::new(MerkleTreeHookContract::new( locator.address, @@ -143,13 +146,7 @@ where #[instrument(level = "debug", err, skip(self))] #[allow(clippy::blocks_in_conditions)] // TODO: `rustc` 1.80.1 clippy issue async fn get_finalized_block_number(&self) -> ChainResult { - Ok(self - .provider - .get_block_number() - .await - .map_err(ChainCommunicationError::from_other)? - .as_u32() - .saturating_sub(self.reorg_period)) + get_finalized_block_number(&self.provider, &self.reorg_period).await } async fn fetch_logs_by_tx_hash( @@ -253,9 +250,13 @@ where M: Middleware + 'static, { #[instrument(skip(self))] - async fn latest_checkpoint(&self, maybe_lag: Option) -> ChainResult { - let call = - call_with_lag(self.contract.latest_checkpoint(), &self.provider, maybe_lag).await?; + async fn latest_checkpoint(&self, reorg_period: &ReorgPeriod) -> ChainResult { + let call = call_with_reorg_period( + self.contract.latest_checkpoint(), + &self.provider, + reorg_period, + ) + .await?; let (root, index) = call.call().await?; Ok(Checkpoint { @@ -268,15 +269,17 @@ where #[instrument(skip(self))] #[allow(clippy::needless_range_loop)] - async fn tree(&self, maybe_lag: Option) -> ChainResult { - let call = call_with_lag(self.contract.tree(), &self.provider, maybe_lag).await?; + async fn tree(&self, reorg_period: &ReorgPeriod) -> ChainResult { + let call = + call_with_reorg_period(self.contract.tree(), &self.provider, reorg_period).await?; Ok(call.call().await?.into()) } #[instrument(skip(self))] - async fn count(&self, maybe_lag: Option) -> ChainResult { - let call = call_with_lag(self.contract.count(), &self.provider, maybe_lag).await?; + async fn count(&self, reorg_period: &ReorgPeriod) -> ChainResult { + let call = + call_with_reorg_period(self.contract.count(), &self.provider, reorg_period).await?; let count = call.call().await?; Ok(count) } diff --git a/rust/main/chains/hyperlane-ethereum/src/contracts/mod.rs b/rust/main/chains/hyperlane-ethereum/src/contracts/mod.rs index 1a39fae07a..f8475f9e73 100644 --- a/rust/main/chains/hyperlane-ethereum/src/contracts/mod.rs +++ b/rust/main/chains/hyperlane-ethereum/src/contracts/mod.rs @@ -1,5 +1,7 @@ pub use {interchain_gas::*, mailbox::*, merkle_tree_hook::*, validator_announce::*}; +pub(crate) use utils::get_finalized_block_number; + mod interchain_gas; mod mailbox; mod merkle_tree_hook; diff --git a/rust/main/chains/hyperlane-ethereum/src/contracts/utils.rs b/rust/main/chains/hyperlane-ethereum/src/contracts/utils.rs index 2905199184..5e9c782013 100644 --- a/rust/main/chains/hyperlane-ethereum/src/contracts/utils.rs +++ b/rust/main/chains/hyperlane-ethereum/src/contracts/utils.rs @@ -6,7 +6,10 @@ use ethers::{ types::{H160 as EthersH160, H256 as EthersH256}, }; use ethers_contract::{ContractError, EthEvent, LogMeta as EthersLogMeta}; -use hyperlane_core::{ChainResult, LogMeta, H512}; +use hyperlane_core::{ChainCommunicationError, ChainResult, LogMeta, H512}; +use tracing::instrument; + +use crate::EthereumReorgPeriod; pub async fn fetch_raw_logs_and_meta( tx_hash: H512, @@ -44,3 +47,33 @@ where .collect(); Ok(logs) } + +#[instrument(level = "trace", err, ret, skip(provider))] +pub async fn get_finalized_block_number( + provider: &M, + reorg_period: &EthereumReorgPeriod, +) -> ChainResult +where + M: Middleware + 'static, +{ + let number = match *reorg_period { + EthereumReorgPeriod::Blocks(blocks) => provider + .get_block_number() + .await + .map_err(ChainCommunicationError::from_other)? + .as_u32() + .saturating_sub(blocks), + + EthereumReorgPeriod::Tag(tag) => provider + .get_block(tag) + .await + .map_err(ChainCommunicationError::from_other)? + .and_then(|block| block.number) + .ok_or(ChainCommunicationError::CustomError( + "Unable to get finalized block number".into(), + ))? + .as_u32(), + }; + + Ok(number) +} diff --git a/rust/main/chains/hyperlane-ethereum/src/tx.rs b/rust/main/chains/hyperlane-ethereum/src/tx.rs index eb42f66d40..2e774622a1 100644 --- a/rust/main/chains/hyperlane-ethereum/src/tx.rs +++ b/rust/main/chains/hyperlane-ethereum/src/tx.rs @@ -1,4 +1,3 @@ -use std::num::NonZeroU64; use std::sync::Arc; use std::time::Duration; @@ -16,10 +15,12 @@ use ethers_core::{ EIP1559_FEE_ESTIMATION_REWARD_PERCENTILE, }, }; -use hyperlane_core::{utils::bytes_to_hex, ChainCommunicationError, ChainResult, H256, U256}; +use hyperlane_core::{ + utils::bytes_to_hex, ChainCommunicationError, ChainResult, ReorgPeriod, H256, U256, +}; use tracing::{debug, error, info, warn}; -use crate::{Middleware, TransactionOverrides}; +use crate::{get_finalized_block_number, EthereumReorgPeriod, Middleware, TransactionOverrides}; /// An amount of gas to add to the estimated gas pub const GAS_ESTIMATE_BUFFER: u32 = 75_000; @@ -216,23 +217,20 @@ where Ok((base_fee_per_gas, max_fee_per_gas, max_priority_fee_per_gas)) } -pub(crate) async fn call_with_lag( +pub(crate) async fn call_with_reorg_period( call: ethers::contract::builders::ContractCall, provider: &M, - maybe_lag: Option, + reorg_period: &ReorgPeriod, ) -> ChainResult> where M: Middleware + 'static, T: Detokenize, { - if let Some(lag) = maybe_lag { - let fixed_block_number: BlockNumber = provider - .get_block_number() - .await - .map_err(ChainCommunicationError::from_other)? - .saturating_sub(lag.get().into()) - .into(); - Ok(call.block(fixed_block_number)) + if !reorg_period.is_none() { + let reorg_period = EthereumReorgPeriod::try_from(reorg_period)?; + let block = get_finalized_block_number(provider, &reorg_period).await? as u64; + + Ok(call.block(block)) } else { Ok(call) } diff --git a/rust/main/chains/hyperlane-fuel/src/mailbox.rs b/rust/main/chains/hyperlane-fuel/src/mailbox.rs index 1c78e839e1..fbe951abef 100644 --- a/rust/main/chains/hyperlane-fuel/src/mailbox.rs +++ b/rust/main/chains/hyperlane-fuel/src/mailbox.rs @@ -10,13 +10,12 @@ use fuels::{ use hyperlane_core::{ utils::bytes_to_hex, ChainCommunicationError, ChainResult, ContractLocator, HyperlaneAbi, HyperlaneChain, HyperlaneContract, HyperlaneDomain, HyperlaneMessage, HyperlaneProvider, - Indexed, Indexer, LogMeta, Mailbox, RawHyperlaneMessage, SequenceAwareIndexer, TxCostEstimate, - TxOutcome, H256, H512, U256, + Indexed, Indexer, LogMeta, Mailbox, RawHyperlaneMessage, ReorgPeriod, SequenceAwareIndexer, + TxCostEstimate, TxOutcome, H256, H512, U256, }; use std::{ collections::HashMap, fmt::{Debug, Formatter}, - num::NonZeroU64, ops::RangeInclusive, }; use tracing::{instrument, warn}; @@ -74,9 +73,9 @@ impl Debug for FuelMailbox { impl Mailbox for FuelMailbox { #[instrument(level = "debug", err, ret, skip(self))] #[allow(clippy::blocks_in_conditions)] // TODO: `rustc` 1.80.1 clippy issue - async fn count(&self, lag: Option) -> ChainResult { + async fn count(&self, reorg_period: &ReorgPeriod) -> ChainResult { assert!( - lag.is_none(), + reorg_period.is_none(), "Fuel does not support querying point-in-time" ); self.contract diff --git a/rust/main/chains/hyperlane-sealevel/src/mailbox.rs b/rust/main/chains/hyperlane-sealevel/src/mailbox.rs index 952599c427..5453360e0b 100644 --- a/rust/main/chains/hyperlane-sealevel/src/mailbox.rs +++ b/rust/main/chains/hyperlane-sealevel/src/mailbox.rs @@ -12,8 +12,8 @@ use hyperlane_core::{ ChainCommunicationError::ContractError, ChainResult, Checkpoint, ContractLocator, Decode as _, Encode as _, FixedPointNumber, HyperlaneAbi, HyperlaneChain, HyperlaneContract, HyperlaneDomain, HyperlaneMessage, HyperlaneProvider, Indexed, Indexer, KnownHyperlaneDomain, - LogMeta, Mailbox, MerkleTreeHook, SequenceAwareIndexer, TxCostEstimate, TxOutcome, H256, H512, - U256, + LogMeta, Mailbox, MerkleTreeHook, ReorgPeriod, SequenceAwareIndexer, TxCostEstimate, TxOutcome, + H256, H512, U256, }; use hyperlane_sealevel_interchain_security_module_interface::{ InterchainSecurityModuleInstruction, VerifyInstruction, @@ -416,8 +416,8 @@ impl std::fmt::Debug for SealevelMailbox { #[async_trait] impl Mailbox for SealevelMailbox { #[instrument(err, ret, skip(self))] - async fn count(&self, _maybe_lag: Option) -> ChainResult { - ::count(self, _maybe_lag).await + async fn count(&self, reorg_period: &ReorgPeriod) -> ChainResult { + ::count(self, reorg_period).await } #[instrument(err, ret, skip(self))] @@ -755,7 +755,7 @@ impl SequenceAwareIndexer for SealevelMailboxIndexer { async fn latest_sequence_count_and_tip(&self) -> ChainResult<(Option, u32)> { let tip = Indexer::::get_finalized_block_number(self).await?; // TODO: need to make sure the call and tip are at the same height? - let count = Mailbox::count(&self.mailbox, None).await?; + let count = Mailbox::count(&self.mailbox, &ReorgPeriod::None).await?; Ok((Some(count), tip)) } } diff --git a/rust/main/chains/hyperlane-sealevel/src/merkle_tree_hook.rs b/rust/main/chains/hyperlane-sealevel/src/merkle_tree_hook.rs index 947f7e70ae..a0813bfba8 100644 --- a/rust/main/chains/hyperlane-sealevel/src/merkle_tree_hook.rs +++ b/rust/main/chains/hyperlane-sealevel/src/merkle_tree_hook.rs @@ -1,11 +1,11 @@ -use std::{num::NonZeroU64, ops::RangeInclusive}; +use std::ops::RangeInclusive; use async_trait::async_trait; use derive_new::new; use hyperlane_core::{ accumulator::incremental::IncrementalMerkle, ChainCommunicationError, ChainResult, Checkpoint, HyperlaneChain, HyperlaneMessage, Indexed, Indexer, LogMeta, MerkleTreeHook, - MerkleTreeInsertion, SequenceAwareIndexer, + MerkleTreeInsertion, ReorgPeriod, SequenceAwareIndexer, }; use hyperlane_sealevel_mailbox::accounts::OutboxAccount; use tracing::instrument; @@ -16,9 +16,9 @@ use crate::{SealevelMailbox, SealevelMailboxIndexer}; impl MerkleTreeHook for SealevelMailbox { #[instrument(err, ret, skip(self))] #[allow(clippy::blocks_in_conditions)] // TODO: `rustc` 1.80.1 clippy issue - async fn tree(&self, lag: Option) -> ChainResult { + async fn tree(&self, reorg_period: &ReorgPeriod) -> ChainResult { assert!( - lag.is_none(), + reorg_period.is_none(), "Sealevel does not support querying point-in-time" ); @@ -35,13 +35,13 @@ impl MerkleTreeHook for SealevelMailbox { #[instrument(err, ret, skip(self))] #[allow(clippy::blocks_in_conditions)] // TODO: `rustc` 1.80.1 clippy issue - async fn latest_checkpoint(&self, lag: Option) -> ChainResult { + async fn latest_checkpoint(&self, reorg_period: &ReorgPeriod) -> ChainResult { assert!( - lag.is_none(), + reorg_period.is_none(), "Sealevel does not support querying point-in-time" ); - let tree = self.tree(lag).await?; + let tree = self.tree(reorg_period).await?; let root = tree.root(); let count: u32 = tree @@ -64,8 +64,8 @@ impl MerkleTreeHook for SealevelMailbox { #[instrument(err, ret, skip(self))] #[allow(clippy::blocks_in_conditions)] // TODO: `rustc` 1.80.1 clippy issue - async fn count(&self, _maybe_lag: Option) -> ChainResult { - let tree = self.tree(_maybe_lag).await?; + async fn count(&self, reorg_period: &ReorgPeriod) -> ChainResult { + let tree = self.tree(reorg_period).await?; tree.count() .try_into() diff --git a/rust/main/hyperlane-base/src/settings/chains.rs b/rust/main/hyperlane-base/src/settings/chains.rs index 8dcb2b9c58..0e08b2d156 100644 --- a/rust/main/hyperlane-base/src/settings/chains.rs +++ b/rust/main/hyperlane-base/src/settings/chains.rs @@ -10,13 +10,13 @@ use hyperlane_core::{ config::OperationBatchConfig, AggregationIsm, CcipReadIsm, ContractLocator, HyperlaneAbi, HyperlaneDomain, HyperlaneDomainProtocol, HyperlaneMessage, HyperlaneProvider, IndexMode, InterchainGasPaymaster, InterchainGasPayment, InterchainSecurityModule, Mailbox, - MerkleTreeHook, MerkleTreeInsertion, MultisigIsm, RoutingIsm, SequenceAwareIndexer, - ValidatorAnnounce, H256, + MerkleTreeHook, MerkleTreeInsertion, MultisigIsm, ReorgPeriod, RoutingIsm, + SequenceAwareIndexer, ValidatorAnnounce, H256, }; use hyperlane_cosmos as h_cosmos; use hyperlane_ethereum::{ self as h_eth, BuildableWithProvider, EthereumInterchainGasPaymasterAbi, EthereumMailboxAbi, - EthereumValidatorAnnounceAbi, + EthereumReorgPeriod, EthereumValidatorAnnounceAbi, }; use hyperlane_fuel as h_fuel; use hyperlane_sealevel as h_sealevel; @@ -45,7 +45,7 @@ pub struct ChainConf { /// Signer configuration for this chain pub signer: Option, /// The reorg period of the chain, i.e. the number of blocks until finality - pub reorg_period: u32, + pub reorg_period: ReorgPeriod, /// Addresses of contracts on the chain pub addresses: CoreContractAddresses, /// The chain connection details @@ -272,13 +272,13 @@ impl ChainConf { match &self.connection { ChainConnectionConf::Ethereum(conf) => { + let reorg_period = + EthereumReorgPeriod::try_from(&self.reorg_period).context(ctx)?; self.build_ethereum( conf, &locator, metrics, - h_eth::SequenceIndexerBuilder { - reorg_period: self.reorg_period, - }, + h_eth::SequenceIndexerBuilder { reorg_period }, ) .await } @@ -289,11 +289,12 @@ impl ChainConf { } ChainConnectionConf::Cosmos(conf) => { let signer = self.cosmos_signer().await.context(ctx)?; + let reorg_period = self.reorg_period.as_blocks().context(ctx)?; let indexer = Box::new(h_cosmos::CosmosMailboxDispatchIndexer::new( conf.clone(), locator, signer, - self.reorg_period, + reorg_period, )?); Ok(indexer as Box>) } @@ -311,13 +312,13 @@ impl ChainConf { match &self.connection { ChainConnectionConf::Ethereum(conf) => { + let reorg_period = + EthereumReorgPeriod::try_from(&self.reorg_period).context(ctx)?; self.build_ethereum( conf, &locator, metrics, - h_eth::DeliveryIndexerBuilder { - reorg_period: self.reorg_period, - }, + h_eth::DeliveryIndexerBuilder { reorg_period }, ) .await } @@ -328,11 +329,12 @@ impl ChainConf { } ChainConnectionConf::Cosmos(conf) => { let signer = self.cosmos_signer().await.context(ctx)?; + let reorg_period = self.reorg_period.as_blocks().context(ctx)?; let indexer = Box::new(h_cosmos::CosmosMailboxDeliveryIndexer::new( conf.clone(), locator, signer, - self.reorg_period, + reorg_period, )?); Ok(indexer as Box>) } @@ -389,13 +391,15 @@ impl ChainConf { match &self.connection { ChainConnectionConf::Ethereum(conf) => { + let reorg_period = + EthereumReorgPeriod::try_from(&self.reorg_period).context(ctx)?; self.build_ethereum( conf, &locator, metrics, h_eth::InterchainGasPaymasterIndexerBuilder { mailbox_address: self.addresses.mailbox.into(), - reorg_period: self.reorg_period, + reorg_period, }, ) .await @@ -408,10 +412,11 @@ impl ChainConf { Ok(indexer as Box>) } ChainConnectionConf::Cosmos(conf) => { + let reorg_period = self.reorg_period.as_blocks().context(ctx)?; let indexer = Box::new(h_cosmos::CosmosInterchainGasPaymasterIndexer::new( conf.clone(), locator, - self.reorg_period, + reorg_period, )?); Ok(indexer as Box>) } @@ -429,13 +434,13 @@ impl ChainConf { match &self.connection { ChainConnectionConf::Ethereum(conf) => { + let reorg_period = + EthereumReorgPeriod::try_from(&self.reorg_period).context(ctx)?; self.build_ethereum( conf, &locator, metrics, - h_eth::MerkleTreeHookIndexerBuilder { - reorg_period: self.reorg_period, - }, + h_eth::MerkleTreeHookIndexerBuilder { reorg_period }, ) .await } @@ -450,12 +455,13 @@ impl ChainConf { } ChainConnectionConf::Cosmos(conf) => { let signer = self.cosmos_signer().await.context(ctx)?; + let reorg_period = self.reorg_period.as_blocks().context(ctx)?; let indexer = Box::new(h_cosmos::CosmosMerkleTreeHookIndexer::new( conf.clone(), locator, // TODO: remove signer requirement entirely signer, - self.reorg_period, + reorg_period, )?); Ok(indexer as Box>) } diff --git a/rust/main/hyperlane-base/src/settings/checkpoint_syncer.rs b/rust/main/hyperlane-base/src/settings/checkpoint_syncer.rs index 38274dafa1..3434a7168a 100644 --- a/rust/main/hyperlane-base/src/settings/checkpoint_syncer.rs +++ b/rust/main/hyperlane-base/src/settings/checkpoint_syncer.rs @@ -180,7 +180,7 @@ mod test { use std::panic::AssertUnwindSafe; use futures_util::FutureExt; - use hyperlane_core::{ReorgEvent, H256}; + use hyperlane_core::{ReorgEvent, ReorgPeriod, H256}; #[tokio::test] async fn test_build_and_validate() { @@ -209,7 +209,7 @@ mod test { .unwrap(); let dummy_checkpoint_index = 56; let unix_timestamp = 1620000000; - let reorg_period = 5; + let reorg_period = ReorgPeriod::from_blocks(5); let dummy_reorg_event = ReorgEvent { local_merkle_root: dummy_local_merkle_root, canonical_merkle_root: dummy_canonical_merkle_root, @@ -237,7 +237,9 @@ mod test { canonical_merkle_root: 0xb437b888332ef12f7260c7f679aad3c96b91ab81c2dc7242f8b290f0b6bba92b, checkpoint_index: 56, unix_timestamp: 1620000000, - reorg_period: 5, + reorg_period: Blocks( + 5, + ), }. Please resolve the reorg to continue."# ); } else { diff --git a/rust/main/hyperlane-base/src/settings/parser/mod.rs b/rust/main/hyperlane-base/src/settings/parser/mod.rs index 65a8c845a3..7c608427bb 100644 --- a/rust/main/hyperlane-base/src/settings/parser/mod.rs +++ b/rust/main/hyperlane-base/src/settings/parser/mod.rs @@ -19,7 +19,7 @@ use url::Url; use h_cosmos::RawCosmosAmount; use hyperlane_core::{ cfg_unwrap_all, config::*, HyperlaneDomain, HyperlaneDomainProtocol, - HyperlaneDomainTechnicalStack, IndexMode, + HyperlaneDomainTechnicalStack, IndexMode, ReorgPeriod, }; use crate::settings::{ @@ -136,8 +136,8 @@ fn parse_chain( .chain(&mut err) .get_opt_key("blocks") .get_key("reorgPeriod") - .parse_u32() - .unwrap_or(1); + .parse_value("Invalid reorgPeriod") + .unwrap_or(ReorgPeriod::from_blocks(1)); let rpcs = parse_base_and_override_urls(&chain, "rpcUrls", "customRpcUrls", "http", &mut err); diff --git a/rust/main/hyperlane-core/src/chain.rs b/rust/main/hyperlane-core/src/chain.rs index bd73ff613f..a4cfaac77c 100644 --- a/rust/main/hyperlane-core/src/chain.rs +++ b/rust/main/hyperlane-core/src/chain.rs @@ -3,16 +3,20 @@ use std::{ fmt::{Debug, Formatter}, hash::{Hash, Hasher}, + num::NonZeroU32, }; use derive_new::new; use num_derive::FromPrimitive; use num_traits::FromPrimitive; -use serde::Serialize; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + #[cfg(feature = "strum")] use strum::{EnumIter, EnumString, IntoStaticStr}; -use crate::{utils::many_to_one, HyperlaneProtocolError, IndexMode, H160, H256}; +use crate::{ + utils::many_to_one, ChainCommunicationError, HyperlaneProtocolError, IndexMode, H160, H256, +}; #[derive(Debug, Clone)] pub struct Address(pub bytes::Bytes); @@ -39,6 +43,80 @@ impl<'a> std::fmt::Display for ContractLocator<'a> { } } +#[derive(Default, Debug, Clone, PartialEq)] +pub enum ReorgPeriod { + #[default] + None, + Blocks(NonZeroU32), + Tag(String), +} + +impl ReorgPeriod { + pub fn from_blocks(blocks: u32) -> Self { + NonZeroU32::try_from(blocks) + .map(ReorgPeriod::Blocks) + .unwrap_or(ReorgPeriod::None) + } + + pub fn as_blocks(&self) -> Result { + match self { + ReorgPeriod::None => Ok(0), + ReorgPeriod::Blocks(blocks) => Ok(blocks.get()), + ReorgPeriod::Tag(_) => Err(ChainCommunicationError::InvalidReorgPeriod(self.clone())), + } + } + + pub fn is_none(&self) -> bool { + matches!(self, ReorgPeriod::None) + } +} + +impl Serialize for ReorgPeriod { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + ReorgPeriod::None => serializer.serialize_u32(0), + ReorgPeriod::Blocks(blocks) => serializer.serialize_u32(blocks.get()), + ReorgPeriod::Tag(tag) => serializer.serialize_str(tag), + } + } +} + +impl<'de> Deserialize<'de> for ReorgPeriod { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + use serde::de; + + struct ReorgPeriodVisitor; + + impl<'de> de::Visitor<'de> for ReorgPeriodVisitor { + type Value = ReorgPeriod; + + fn expecting(&self, f: &mut Formatter) -> std::fmt::Result { + f.write_str("reorgPeriod as a number or string") + } + + fn visit_u64(self, v: u64) -> Result { + let v = v.try_into().map_err(de::Error::custom)?; + Ok(ReorgPeriod::from_blocks(v)) + } + + fn visit_str(self, v: &str) -> Result { + match v.parse::() { + Ok(v) => self.visit_u32(v), + Err(_) => Ok(ReorgPeriod::Tag(v.to_string())), + } + } + } + + deserializer.deserialize_any(ReorgPeriodVisitor) + } +} + /// All domains supported by Hyperlane. #[derive(FromPrimitive, PartialEq, Eq, Debug, Clone, Copy, Hash, Serialize)] #[cfg_attr( @@ -505,9 +583,9 @@ impl HyperlaneDomain { #[cfg(test)] #[cfg(feature = "strum")] mod tests { - use std::str::FromStr; + use std::{num::NonZeroU32, str::FromStr}; - use crate::KnownHyperlaneDomain; + use crate::{KnownHyperlaneDomain, ReorgPeriod}; #[test] fn domain_strings() { @@ -560,4 +638,32 @@ mod tests { ); assert!("foo".parse::().is_err()); } + + #[test] + fn parse_reorg_period() { + assert_eq!( + serde_json::from_value::(0.into()).unwrap(), + ReorgPeriod::None + ); + + assert_eq!( + serde_json::from_value::("0".into()).unwrap(), + ReorgPeriod::None + ); + + assert_eq!( + serde_json::from_value::(12.into()).unwrap(), + ReorgPeriod::Blocks(NonZeroU32::new(12).unwrap()) + ); + + assert_eq!( + serde_json::from_value::("12".into()).unwrap(), + ReorgPeriod::Blocks(NonZeroU32::new(12).unwrap()) + ); + + assert_eq!( + serde_json::from_value::("finalized".into()).unwrap(), + ReorgPeriod::Tag("finalized".into()) + ); + } } diff --git a/rust/main/hyperlane-core/src/error.rs b/rust/main/hyperlane-core/src/error.rs index 56bbf4a826..fe1385c0ed 100644 --- a/rust/main/hyperlane-core/src/error.rs +++ b/rust/main/hyperlane-core/src/error.rs @@ -10,8 +10,10 @@ use crate::config::StrOrIntParseError; use crate::rpc_clients::RpcClientError; use std::string::FromUtf8Error; -use crate::HyperlaneProviderError; -use crate::{Error as PrimitiveTypeError, HyperlaneSignerError, H256, U256}; +use crate::{ + Error as PrimitiveTypeError, HyperlaneProviderError, HyperlaneSignerError, ReorgPeriod, H256, + U256, +}; /// The result of interacting with a chain. pub type ChainResult = Result; @@ -157,6 +159,9 @@ pub enum ChainCommunicationError { /// Hyperlane signer error #[error("{0}")] HyperlaneSignerError(#[from] HyperlaneSignerError), + /// Invalid reorg period + #[error("Invalid reorg period: {0:?}")] + InvalidReorgPeriod(ReorgPeriod), } impl ChainCommunicationError { diff --git a/rust/main/hyperlane-core/src/traits/mailbox.rs b/rust/main/hyperlane-core/src/traits/mailbox.rs index d5e9081b65..83646e659b 100644 --- a/rust/main/hyperlane-core/src/traits/mailbox.rs +++ b/rust/main/hyperlane-core/src/traits/mailbox.rs @@ -1,12 +1,11 @@ use std::fmt::Debug; -use std::num::NonZeroU64; use async_trait::async_trait; use derive_new::new; use crate::{ traits::TxOutcome, utils::domain_hash, BatchItem, ChainCommunicationError, ChainResult, - HyperlaneContract, HyperlaneMessage, QueueOperation, TxCostEstimate, H256, U256, + HyperlaneContract, HyperlaneMessage, QueueOperation, ReorgPeriod, TxCostEstimate, H256, U256, }; /// Interface for the Mailbox chain contract. Allows abstraction over different @@ -20,9 +19,9 @@ pub trait Mailbox: HyperlaneContract + Send + Sync + Debug { /// Gets the current leaf count of the merkle tree /// - /// - `lag` is how far behind the current block to query, if not specified + /// - `reorg_period` is how far behind the current block to query, if not specified /// it will query at the latest block. - async fn count(&self, lag: Option) -> ChainResult; + async fn count(&self, reorg_period: &ReorgPeriod) -> ChainResult; /// Fetch the status of a message async fn delivered(&self, id: H256) -> ChainResult; diff --git a/rust/main/hyperlane-core/src/traits/merkle_tree_hook.rs b/rust/main/hyperlane-core/src/traits/merkle_tree_hook.rs index 35f3fa4efb..183ca73837 100644 --- a/rust/main/hyperlane-core/src/traits/merkle_tree_hook.rs +++ b/rust/main/hyperlane-core/src/traits/merkle_tree_hook.rs @@ -1,11 +1,11 @@ use std::fmt::Debug; -use std::num::NonZeroU64; use async_trait::async_trait; use auto_impl::auto_impl; use crate::{ accumulator::incremental::IncrementalMerkle, ChainResult, Checkpoint, HyperlaneContract, + ReorgPeriod, }; /// Interface for the MerkleTreeHook chain contract. Allows abstraction over different @@ -15,19 +15,19 @@ use crate::{ pub trait MerkleTreeHook: HyperlaneContract + Send + Sync + Debug { /// Return the incremental merkle tree in storage /// - /// - `lag` is how far behind the current block to query, if not specified + /// - `reorg_period` is how far behind the current block to query, if not specified /// it will query at the latest block. - async fn tree(&self, lag: Option) -> ChainResult; + async fn tree(&self, reorg_period: &ReorgPeriod) -> ChainResult; /// Gets the current leaf count of the merkle tree /// - /// - `lag` is how far behind the current block to query, if not specified + /// - `reorg_period` is how far behind the current block to query, if not specified /// it will query at the latest block. - async fn count(&self, lag: Option) -> ChainResult; + async fn count(&self, reorg_period: &ReorgPeriod) -> ChainResult; /// Get the latest checkpoint. /// - /// - `lag` is how far behind the current block to query, if not specified + /// - `reorg_period` is how far behind the current block to query, if not specified /// it will query at the latest block. - async fn latest_checkpoint(&self, lag: Option) -> ChainResult; + async fn latest_checkpoint(&self, reorg_period: &ReorgPeriod) -> ChainResult; } diff --git a/rust/main/hyperlane-core/src/types/reorg.rs b/rust/main/hyperlane-core/src/types/reorg.rs index 00a8dedc12..eb41844c0e 100644 --- a/rust/main/hyperlane-core/src/types/reorg.rs +++ b/rust/main/hyperlane-core/src/types/reorg.rs @@ -1,7 +1,7 @@ use derive_new::new; use serde::{Deserialize, Serialize}; -use crate::H256; +use crate::{ReorgPeriod, H256}; /// Details about a detected chain reorg, from an agent's perspective #[derive(Debug, Clone, Serialize, Deserialize, new)] @@ -15,6 +15,6 @@ pub struct ReorgEvent { pub checkpoint_index: u32, /// the timestamp when the reorg was detected, in seconds since the Unix epoch pub unix_timestamp: u64, - /// the reorg period configured for the agent, in blocks - pub reorg_period: u64, + /// the reorg period configured for the agent + pub reorg_period: ReorgPeriod, } diff --git a/rust/main/hyperlane-test/src/mocks/mailbox.rs b/rust/main/hyperlane-test/src/mocks/mailbox.rs index dc09e1026f..7333e2c8a3 100644 --- a/rust/main/hyperlane-test/src/mocks/mailbox.rs +++ b/rust/main/hyperlane-test/src/mocks/mailbox.rs @@ -1,7 +1,5 @@ #![allow(non_snake_case)] -use std::num::NonZeroU64; - use async_trait::async_trait; use mockall::*; @@ -28,11 +26,11 @@ mock! { nonce: usize, ) -> ChainResult> {} - pub fn _tree(&self, maybe_lag: Option) -> ChainResult {} + pub fn _tree(&self, reorg_period: &ReorgPeriod) -> ChainResult {} - pub fn _count(&self, maybe_lag: Option) -> ChainResult {} + pub fn _count(&self, reorg_period: &ReorgPeriod) -> ChainResult {} - pub fn _latest_checkpoint(&self, maybe_lag: Option) -> ChainResult {} + pub fn _latest_checkpoint(&self, reorg_period: &ReorgPeriod) -> ChainResult {} pub fn _default_ism(&self) -> ChainResult {} pub fn _recipient_ism(&self, recipient: H256) -> ChainResult {} @@ -68,8 +66,8 @@ impl std::fmt::Debug for MockMailboxContract { #[async_trait] impl Mailbox for MockMailboxContract { - async fn count(&self, maybe_lag: Option) -> ChainResult { - self._count(maybe_lag) + async fn count(&self, reorg_period: &ReorgPeriod) -> ChainResult { + self._count(reorg_period) } async fn default_ism(&self) -> ChainResult { diff --git a/typescript/cli/src/config/chain.ts b/typescript/cli/src/config/chain.ts index 199e026c82..d3279a2029 100644 --- a/typescript/cli/src/config/chain.ts +++ b/typescript/cli/src/config/chain.ts @@ -168,6 +168,11 @@ async function addBlockOrGasConfig(metadata: ChainMetadata): Promise { } async function addBlockConfig(metadata: ChainMetadata): Promise { + const parseReorgPeriod = (value: string) => { + const parsed = parseInt(value, 10); + return isNaN(parsed) ? value : parsed; + }; + const wantBlockConfig = await confirm({ message: 'Do you want to add block config for this chain', }); @@ -179,8 +184,10 @@ async function addBlockConfig(metadata: ChainMetadata): Promise { }); const blockReorgPeriod = await input({ message: - 'Enter no. of blocks before a transaction has a near-zero chance of reverting (0-500):', - validate: (value) => parseInt(value) >= 0 && parseInt(value) <= 500, + 'Enter no. of blocks before a transaction has a near-zero chance of reverting (0-500) or block tag:', + validate: (value) => + isNaN(parseInt(value)) || + (parseInt(value) >= 0 && parseInt(value) <= 500), }); const blockTimeEstimate = await input({ message: 'Enter the rough estimate of time per block in seconds (0-20):', @@ -188,7 +195,7 @@ async function addBlockConfig(metadata: ChainMetadata): Promise { }); metadata.blocks = { confirmations: parseInt(blockConfirmation, 10), - reorgPeriod: parseInt(blockReorgPeriod, 10), + reorgPeriod: parseReorgPeriod(blockReorgPeriod), estimateBlockTime: parseInt(blockTimeEstimate, 10), }; } diff --git a/typescript/infra/config/registry.ts b/typescript/infra/config/registry.ts index 51ac877295..4b72c9f5be 100644 --- a/typescript/infra/config/registry.ts +++ b/typescript/infra/config/registry.ts @@ -78,7 +78,7 @@ export function getDomainId(chainName: ChainName): number { return resolveDomainId(chain); } -export function getReorgPeriod(chainName: ChainName): number { +export function getReorgPeriod(chainName: ChainName): string | number { const chain = getChain(chainName); return resolveReorgPeriod(chain); } diff --git a/typescript/infra/src/config/agent/validator.ts b/typescript/infra/src/config/agent/validator.ts index 05293abfa4..e18220017c 100644 --- a/typescript/infra/src/config/agent/validator.ts +++ b/typescript/infra/src/config/agent/validator.ts @@ -26,8 +26,8 @@ export type ValidatorBaseChainConfigMap = ChainMap; export interface ValidatorBaseChainConfig { // How frequently to check for new checkpoints interval: number; - // The reorg_period in blocks; overrides chain metadata - reorgPeriod: number; + // The reorg_period in blocks or block tag; overrides chain metadata + reorgPeriod: string | number; // Individual validator agents validators: Array; } diff --git a/typescript/sdk/src/metadata/chainMetadata.test.ts b/typescript/sdk/src/metadata/chainMetadata.test.ts index 773eb9c033..65a980f834 100644 --- a/typescript/sdk/src/metadata/chainMetadata.test.ts +++ b/typescript/sdk/src/metadata/chainMetadata.test.ts @@ -62,6 +62,16 @@ describe('ChainMetadataSchema', () => { grpcUrls: [], }), ).to.eq(true); + + expect( + isValidChainMetadata({ + ...minimalSchema, + blocks: { + confirmations: 1, + reorgPeriod: 'finalized', + }, + }), + ).to.eq(true); }); it('Rejects invalid schemas', () => { diff --git a/typescript/sdk/src/metadata/chainMetadataTypes.ts b/typescript/sdk/src/metadata/chainMetadataTypes.ts index caf290ced5..cf6c9bb54b 100644 --- a/typescript/sdk/src/metadata/chainMetadataTypes.ts +++ b/typescript/sdk/src/metadata/chainMetadataTypes.ts @@ -125,9 +125,12 @@ export const ChainMetadataSchemaObject = z.object({ confirmations: ZUint.describe( 'Number of blocks to wait before considering a transaction confirmed.', ), - reorgPeriod: ZUint.optional().describe( - 'Number of blocks before a transaction has a near-zero chance of reverting.', - ), + reorgPeriod: z + .union([ZUint, z.string()]) + .optional() + .describe( + 'Number of blocks before a transaction has a near-zero chance of reverting or block tag.', + ), estimateBlockTime: z .number() .positive() @@ -371,7 +374,7 @@ export function getChainIdNumber(chainMetadata: ChainMetadata): number { else throw new Error('ChainId is not a number, chain may be of Cosmos type'); } -export function getReorgPeriod(chainMetadata: ChainMetadata): number { +export function getReorgPeriod(chainMetadata: ChainMetadata): string | number { if (chainMetadata.blocks?.reorgPeriod !== undefined) return chainMetadata.blocks.reorgPeriod; else throw new Error('Chain has no reorg period'); From 2cc51c1640db7757a08ecd16241a49efd17aff3c Mon Sep 17 00:00:00 2001 From: Paul Balaji <10051819+paulbalaji@users.noreply.github.com> Date: Wed, 23 Oct 2024 16:07:19 +0100 Subject: [PATCH 03/61] chore: split cli e2e into separate job (#4733) ### Description chore: split cli e2e into separate job before: ![image](https://github.com/user-attachments/assets/bb19243f-fc38-478e-b94c-f39020756964) after: ![image](https://github.com/user-attachments/assets/6ebcb783-1d07-4350-8823-5cc5df7c5c65) ### Drive-by changes na ### Related issues `yarn-test` step sometimes taking even 15 mins nowadays, half of which is e2e tests ### Backward compatibility yes ### Testing ci --- .github/workflows/test.yml | 26 ++++++++++++++++++- typescript/cli/.mocharc-e2e.json | 8 ++++++ typescript/cli/.mocharc.json | 2 +- typescript/cli/package.json | 3 ++- .../scripts/{all-test.sh => run-e2e-test.sh} | 12 ++++----- 5 files changed, 42 insertions(+), 9 deletions(-) create mode 100644 typescript/cli/.mocharc-e2e.json rename typescript/cli/scripts/{all-test.sh => run-e2e-test.sh} (71%) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b370750024..bfbb3c8f6d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -109,6 +109,30 @@ jobs: - name: Unit Tests run: yarn test:ci + cli-e2e: + runs-on: ubuntu-latest + needs: [yarn-install] + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + submodules: recursive + fetch-depth: 0 + + - name: foundry-install + uses: foundry-rs/foundry-toolchain@v1 + + - name: yarn-build + uses: ./.github/actions/yarn-build-with-cache + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + + - name: Checkout registry + uses: ./.github/actions/checkout-registry + + - name: CLI e2e tests + run: yarn --cwd typescript/cli test:e2e + agent-configs: runs-on: ubuntu-latest needs: [yarn-install] @@ -218,7 +242,7 @@ jobs: run: | if [[ -n "$(git diff ${{ github.event.pull_request.head.sha || github.sha }} ${{ github.event.pull_request.base.sha }} -- ./rust)" ]]; then echo "rust_changes=true" >> $GITHUB_OUTPUT - echo "$(git diff ${{ github.event.pull_request.head.sha || github.sha }} ${{ github.event.pull_request.base.sha }} -- ./rust)" + echo "$(git diff ${{ github.event.pull_request.head.sha || github.sha }} ${{ github.event.pull_request.base.sha }} -- ./rust)" else echo "rust_changes=false" >> $GITHUB_OUTPUT fi diff --git a/typescript/cli/.mocharc-e2e.json b/typescript/cli/.mocharc-e2e.json new file mode 100644 index 0000000000..ecabac62f3 --- /dev/null +++ b/typescript/cli/.mocharc-e2e.json @@ -0,0 +1,8 @@ +{ + "extensions": ["ts"], + "spec": ["src/**/*.e2e-test.ts"], + "node-option": [ + "experimental-specifier-resolution=node", + "loader=ts-node/esm" + ] +} diff --git a/typescript/cli/.mocharc.json b/typescript/cli/.mocharc.json index 17ba33e2f7..2d7433a2f1 100644 --- a/typescript/cli/.mocharc.json +++ b/typescript/cli/.mocharc.json @@ -1,6 +1,6 @@ { "extensions": ["ts"], - "spec": ["src/**/*.test.*", "src/**/*.e2e-test.ts"], + "spec": ["src/**/*.test.*"], "node-option": [ "experimental-specifier-resolution=node", "loader=ts-node/esm" diff --git a/typescript/cli/package.json b/typescript/cli/package.json index ee91e5fc83..a014c8bbc3 100644 --- a/typescript/cli/package.json +++ b/typescript/cli/package.json @@ -47,7 +47,8 @@ "clean": "rm -rf ./dist", "lint": "eslint . --ext .ts", "prettier": "prettier --write ./src ./examples", - "test:ci": "./scripts/all-test.sh", + "test:ci": "yarn mocha --config .mocharc.json", + "test:e2e": "./scripts/run-e2e-test.sh", "version:update": "echo \"export const VERSION = '$npm_package_version';\" > src/version.ts" }, "files": [ diff --git a/typescript/cli/scripts/all-test.sh b/typescript/cli/scripts/run-e2e-test.sh similarity index 71% rename from typescript/cli/scripts/all-test.sh rename to typescript/cli/scripts/run-e2e-test.sh index f330609870..e398253b2e 100755 --- a/typescript/cli/scripts/all-test.sh +++ b/typescript/cli/scripts/run-e2e-test.sh @@ -1,24 +1,24 @@ #!/usr/bin/env bash function cleanup() { - set +e + set +e pkill -f anvil rm -rf /tmp/anvil2 rm -rf /tmp/anvil3 rm -f ./test-configs/anvil/chains/anvil2/addresses.yaml rm -f ./test-configs/anvil/chains/anvil3/addresses.yaml - set -e + set -e } cleanup -echo "Starting anvil2 and anvil3 chain" +echo "Starting anvil2 and anvil3 chain for E2E tests" anvil --chain-id 31338 -p 8555 --state /tmp/anvil2/state --gas-price 1 > /dev/null & anvil --chain-id 31347 -p 8600 --state /tmp/anvil3/state --gas-price 1 > /dev/null & -echo "Running all tests" -yarn mocha --config .mocharc.json +echo "Running E2E tests" +yarn mocha --config .mocharc-e2e.json cleanup -echo "Done all tests" \ No newline at end of file +echo "Completed E2E tests" From 937392b61d3d00e915eaf92566f1df86fa372c05 Mon Sep 17 00:00:00 2001 From: Daniel Savu <23065004+daniel-savu@users.noreply.github.com> Date: Wed, 23 Oct 2024 17:49:55 +0100 Subject: [PATCH 04/61] chore(evm): optimize block number fetching (#4736) ### Description Fix for leftover [comment](https://github.com/hyperlane-xyz/hyperlane-monorepo/pull/4629#discussion_r1811084234) from https://github.com/hyperlane-xyz/hyperlane-monorepo/pull/4629 ### Drive-by changes ### Related issues ### Backward compatibility ### Testing --- .../chains/hyperlane-ethereum/src/config.rs | 22 ++++++++++++++++++- rust/main/chains/hyperlane-ethereum/src/tx.rs | 10 ++++----- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/rust/main/chains/hyperlane-ethereum/src/config.rs b/rust/main/chains/hyperlane-ethereum/src/config.rs index 0e2a2a13aa..e1375e790d 100644 --- a/rust/main/chains/hyperlane-ethereum/src/config.rs +++ b/rust/main/chains/hyperlane-ethereum/src/config.rs @@ -1,5 +1,8 @@ +use ethers::providers::Middleware; use ethers_core::types::{BlockId, BlockNumber}; -use hyperlane_core::{config::OperationBatchConfig, ChainCommunicationError, ReorgPeriod, U256}; +use hyperlane_core::{ + config::OperationBatchConfig, ChainCommunicationError, ChainResult, ReorgPeriod, U256, +}; use url::Url; /// Ethereum RPC connection configuration @@ -84,3 +87,20 @@ impl TryFrom<&ReorgPeriod> for EthereumReorgPeriod { } } } + +impl EthereumReorgPeriod { + /// Converts the reorg period into a block id + pub async fn into_block_id( + &self, + provider: &M, + ) -> ChainResult { + let block_id = match self { + EthereumReorgPeriod::Blocks(_) => { + (crate::get_finalized_block_number(provider, self).await? as u64).into() + } + // no need to fetch the block number for the `tag` + EthereumReorgPeriod::Tag(tag) => *tag, + }; + Ok(block_id) + } +} diff --git a/rust/main/chains/hyperlane-ethereum/src/tx.rs b/rust/main/chains/hyperlane-ethereum/src/tx.rs index 2e774622a1..05cc4da44e 100644 --- a/rust/main/chains/hyperlane-ethereum/src/tx.rs +++ b/rust/main/chains/hyperlane-ethereum/src/tx.rs @@ -20,7 +20,7 @@ use hyperlane_core::{ }; use tracing::{debug, error, info, warn}; -use crate::{get_finalized_block_number, EthereumReorgPeriod, Middleware, TransactionOverrides}; +use crate::{EthereumReorgPeriod, Middleware, TransactionOverrides}; /// An amount of gas to add to the estimated gas pub const GAS_ESTIMATE_BUFFER: u32 = 75_000; @@ -227,10 +227,10 @@ where T: Detokenize, { if !reorg_period.is_none() { - let reorg_period = EthereumReorgPeriod::try_from(reorg_period)?; - let block = get_finalized_block_number(provider, &reorg_period).await? as u64; - - Ok(call.block(block)) + let block_id = EthereumReorgPeriod::try_from(reorg_period)? + .into_block_id(provider) + .await?; + Ok(call.block(block_id)) } else { Ok(call) } From 5fc442b97fdcde19ea4513ef01eab9b3d5463034 Mon Sep 17 00:00:00 2001 From: Daniel Savu <23065004+daniel-savu@users.noreply.github.com> Date: Wed, 23 Oct 2024 17:52:46 +0100 Subject: [PATCH 05/61] chore: add sleep log in `fetch_logs_with_cursor` (#4734) ### Description ### Drive-by changes ### Related issues ### Backward compatibility ### Testing --- rust/main/config/mainnet_config.json | 6 ++++-- .../hyperlane-base/src/contract_sync/mod.rs | 17 ++++++++++++----- .../hyperlane-base/src/settings/parser/mod.rs | 4 +++- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/rust/main/config/mainnet_config.json b/rust/main/config/mainnet_config.json index 2667590feb..dc0551f9d1 100644 --- a/rust/main/config/mainnet_config.json +++ b/rust/main/config/mainnet_config.json @@ -784,7 +784,8 @@ "gasCurrencyCoinGeckoId": "ethereum", "index": { "from": 1, - "mode": "sequence" + "mode": "sequence", + "chunk": 100 }, "interchainGasPaymaster": "ABb3i11z7wKoGCfeRQNQbVYWjAm7jG7HzZnDLV4RKRbK", "mailbox": "EitxJuv2iBjsg2d7jVy2LDC1e2zBrx4GB5Y9h2Ko3A9Y", @@ -2804,7 +2805,8 @@ "gasCurrencyCoinGeckoId": "solana", "index": { "from": 1, - "mode": "sequence" + "mode": "sequence", + "chunk": 100 }, "interchainGasPaymaster": "JAvHW21tYXE9dtdG83DReqU2b4LUexFuCbtJT5tF8X6M", "mailbox": "E588QtVUvresuXq2KoNEwAmoifCzYGpRBdHByN9KQMbi", diff --git a/rust/main/hyperlane-base/src/contract_sync/mod.rs b/rust/main/hyperlane-base/src/contract_sync/mod.rs index 5105a1ade3..8e6f8278f1 100644 --- a/rust/main/hyperlane-base/src/contract_sync/mod.rs +++ b/rust/main/hyperlane-base/src/contract_sync/mod.rs @@ -170,7 +170,7 @@ where Ok(logs) => logs, Err(err) => { warn!(?err, ?range, "Error fetching logs in range"); - break SLEEP_DURATION; + break Some(SLEEP_DURATION); } }; @@ -196,13 +196,20 @@ where // Update cursor if let Err(err) = cursor.update(logs, range).await { warn!(?err, "Error updating cursor"); - break SLEEP_DURATION; + break Some(SLEEP_DURATION); }; - break Default::default(); + break None; }, - CursorAction::Sleep(duration) => duration, + CursorAction::Sleep(duration) => Some(duration), }; - sleep(sleep_duration).await + if let Some(sleep_duration) = sleep_duration { + debug!( + cursor = ?cursor, + ?sleep_duration, + "Cursor can't make progress, sleeping", + ); + sleep(sleep_duration).await + } } async fn dedupe_and_store_logs( diff --git a/rust/main/hyperlane-base/src/settings/parser/mod.rs b/rust/main/hyperlane-base/src/settings/parser/mod.rs index 7c608427bb..d176ad857b 100644 --- a/rust/main/hyperlane-base/src/settings/parser/mod.rs +++ b/rust/main/hyperlane-base/src/settings/parser/mod.rs @@ -34,6 +34,8 @@ pub use self::json_value_parser::ValueParser; mod connection_parser; mod json_value_parser; +const DEFAULT_CHUNK_SIZE: u32 = 1999; + /// The base agent config #[derive(Debug, Deserialize)] #[serde(transparent)] @@ -152,7 +154,7 @@ fn parse_chain( .get_opt_key("index") .get_opt_key("chunk") .parse_u32() - .unwrap_or(1999); + .unwrap_or(DEFAULT_CHUNK_SIZE); let mode = chain .chain(&mut err) .get_opt_key("index") From d41aa6928553788df395f2880043e7ff28cede77 Mon Sep 17 00:00:00 2001 From: Paul Balaji <10051819+paulbalaji@users.noreply.github.com> Date: Wed, 23 Oct 2024 18:20:17 +0100 Subject: [PATCH 06/61] feat: add block tag enum for validating reorgPeriod (#4739) ### Description - feat: add `EthJsonRpcBlockParameterTag` enum for validating reorgPeriod - cli will check that reorgPeriod is either a number or one of the appropriate tags, but the underlying schema is still left loose to be forwards-compatible - exporting the enum will also let us use this in unit tests on the registry side ### Drive-by changes na ### Related issues na ### Backward compatibility ye ### Testing ci --------- Signed-off-by: pbio <10051819+paulbalaji@users.noreply.github.com> --- .changeset/quiet-cooks-join.md | 6 ++++++ typescript/cli/src/config/chain.ts | 21 +++++++++++++------ typescript/sdk/src/index.ts | 1 + .../sdk/src/metadata/chainMetadata.test.ts | 8 +++++-- .../sdk/src/metadata/chainMetadataTypes.ts | 8 +++++++ 5 files changed, 36 insertions(+), 8 deletions(-) create mode 100644 .changeset/quiet-cooks-join.md diff --git a/.changeset/quiet-cooks-join.md b/.changeset/quiet-cooks-join.md new file mode 100644 index 0000000000..a5f39f2ab4 --- /dev/null +++ b/.changeset/quiet-cooks-join.md @@ -0,0 +1,6 @@ +--- +'@hyperlane-xyz/cli': minor +'@hyperlane-xyz/sdk': minor +--- + +Add `EthJsonRpcBlockParameterTag` enum for validating reorgPeriod diff --git a/typescript/cli/src/config/chain.ts b/typescript/cli/src/config/chain.ts index d3279a2029..e710b6cf00 100644 --- a/typescript/cli/src/config/chain.ts +++ b/typescript/cli/src/config/chain.ts @@ -5,6 +5,7 @@ import { stringify as yamlStringify } from 'yaml'; import { ChainMetadata, ChainMetadataSchema, + EthJsonRpcBlockParameterTag, ExplorerFamily, ZChainName, } from '@hyperlane-xyz/sdk'; @@ -168,9 +169,11 @@ async function addBlockOrGasConfig(metadata: ChainMetadata): Promise { } async function addBlockConfig(metadata: ChainMetadata): Promise { - const parseReorgPeriod = (value: string) => { + const parseReorgPeriod = ( + value: string, + ): number | EthJsonRpcBlockParameterTag => { const parsed = parseInt(value, 10); - return isNaN(parsed) ? value : parsed; + return isNaN(parsed) ? (value as EthJsonRpcBlockParameterTag) : parsed; }; const wantBlockConfig = await confirm({ @@ -184,10 +187,16 @@ async function addBlockConfig(metadata: ChainMetadata): Promise { }); const blockReorgPeriod = await input({ message: - 'Enter no. of blocks before a transaction has a near-zero chance of reverting (0-500) or block tag:', - validate: (value) => - isNaN(parseInt(value)) || - (parseInt(value) >= 0 && parseInt(value) <= 500), + 'Enter no. of blocks before a transaction has a near-zero chance of reverting (0-500) or block tag (earliest, latest, safe, finalized, pending):', + validate: (value) => { + const parsedInt = parseInt(value, 10); + return ( + Object.values(EthJsonRpcBlockParameterTag).includes( + value as EthJsonRpcBlockParameterTag, + ) || + (!isNaN(parsedInt) && parsedInt >= 0 && parsedInt <= 500) + ); + }, }); const blockTimeEstimate = await input({ message: 'Enter the rough estimate of time per block in seconds (0-20):', diff --git a/typescript/sdk/src/index.ts b/typescript/sdk/src/index.ts index 415a11d269..e9bd31bb42 100644 --- a/typescript/sdk/src/index.ts +++ b/typescript/sdk/src/index.ts @@ -196,6 +196,7 @@ export { export { BlockExplorer, BlockExplorerSchema, + EthJsonRpcBlockParameterTag, ChainMetadata, ChainMetadataSchema, ChainMetadataSchemaObject, diff --git a/typescript/sdk/src/metadata/chainMetadata.test.ts b/typescript/sdk/src/metadata/chainMetadata.test.ts index 65a980f834..f38a59e46d 100644 --- a/typescript/sdk/src/metadata/chainMetadata.test.ts +++ b/typescript/sdk/src/metadata/chainMetadata.test.ts @@ -2,7 +2,11 @@ import { expect } from 'chai'; import { ProtocolType } from '@hyperlane-xyz/utils'; -import { ChainMetadata, isValidChainMetadata } from './chainMetadataTypes.js'; +import { + ChainMetadata, + EthJsonRpcBlockParameterTag, + isValidChainMetadata, +} from './chainMetadataTypes.js'; const minimalSchema: ChainMetadata = { chainId: 5, @@ -68,7 +72,7 @@ describe('ChainMetadataSchema', () => { ...minimalSchema, blocks: { confirmations: 1, - reorgPeriod: 'finalized', + reorgPeriod: EthJsonRpcBlockParameterTag.Finalized, }, }), ).to.eq(true); diff --git a/typescript/sdk/src/metadata/chainMetadataTypes.ts b/typescript/sdk/src/metadata/chainMetadataTypes.ts index cf6c9bb54b..bf41cd4ac2 100644 --- a/typescript/sdk/src/metadata/chainMetadataTypes.ts +++ b/typescript/sdk/src/metadata/chainMetadataTypes.ts @@ -10,6 +10,14 @@ import { ChainMap } from '../types.js'; import { ZChainName, ZNzUint, ZUint } from './customZodTypes.js'; +export enum EthJsonRpcBlockParameterTag { + Earliest = 'earliest', + Latest = 'latest', + Safe = 'safe', + Finalized = 'finalized', + Pending = 'pending', +} + export enum ExplorerFamily { Etherscan = 'etherscan', Blockscout = 'blockscout', From 7f3e0669d284ad7d6826443573abd4a23f6c423f Mon Sep 17 00:00:00 2001 From: Paul Balaji <10051819+paulbalaji@users.noreply.github.com> Date: Wed, 23 Oct 2024 18:51:05 +0100 Subject: [PATCH 07/61] feat: add stride to relayed chains (#4716) ### Description - feat: add stride to relayer - feat: add stride to scraper ### Drive-by changes - fix filtering non-evm addresses in appFromAddressesMapHelper - fallback to manual `yarn install` in `yarn-build-with-cache` action - fix bug in app governor to make inevm env-test pass ### Related issues na ### Backward compatibility yes ### Testing manual --------- Signed-off-by: pbio <10051819+paulbalaji@users.noreply.github.com> --- .changeset/tough-bats-yell.md | 5 + .../actions/yarn-build-with-cache/action.yml | 16 +- .registryrc | 2 +- .../m20230309_000001_create_table_domain.rs | 8 + rust/main/config/mainnet_config.json | 206 ++++++++++++------ .../config/environments/mainnet3/agent.ts | 9 +- .../config/environments/mainnet3/funding.ts | 2 + .../environments/mainnet3/gasPrices.json | 4 + .../mainnet3/supportedChainNames.ts | 1 + .../environments/mainnet3/tokenPrices.json | 1 + .../infra/src/govern/HyperlaneAppGovernor.ts | 24 +- typescript/sdk/src/contracts/contracts.ts | 32 +-- 12 files changed, 204 insertions(+), 106 deletions(-) create mode 100644 .changeset/tough-bats-yell.md diff --git a/.changeset/tough-bats-yell.md b/.changeset/tough-bats-yell.md new file mode 100644 index 0000000000..71343609bc --- /dev/null +++ b/.changeset/tough-bats-yell.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/sdk': patch +--- + +Fix filtering non-evm addresses in appFromAddressesMapHelper diff --git a/.github/actions/yarn-build-with-cache/action.yml b/.github/actions/yarn-build-with-cache/action.yml index 7ffad99a00..2aec33acc3 100644 --- a/.github/actions/yarn-build-with-cache/action.yml +++ b/.github/actions/yarn-build-with-cache/action.yml @@ -11,12 +11,26 @@ runs: steps: - name: Cache uses: buildjet/cache@v4 + id: cache with: path: | **/node_modules .yarn key: ${{ runner.os }}-yarn-cache-${{ hashFiles('./yarn.lock') }} - fail-on-cache-miss: true + + # Typically, the cache will be hit, but if there's a network error when + # restoring the cache, let's run the install step ourselves. + - name: Install dependencies + if: steps.cache.outputs.cache-hit != 'true' + shell: bash + run: | + yarn install + CHANGES=$(git status -s --ignore-submodules) + if [[ ! -z $CHANGES ]]; then + echo "Changes found: $CHANGES" + git diff + exit 1 + fi - name: Build shell: bash diff --git a/.registryrc b/.registryrc index 44edec0c46..862c96776e 100644 --- a/.registryrc +++ b/.registryrc @@ -1 +1 @@ -8583d0841615313c8c880e765eba760378e061cd +dee58183e51f4eb43e84dbac0e595a4b389dbe80 diff --git a/rust/main/agents/scraper/migration/src/m20230309_000001_create_table_domain.rs b/rust/main/agents/scraper/migration/src/m20230309_000001_create_table_domain.rs index 80dcfc6222..3a89e313dc 100644 --- a/rust/main/agents/scraper/migration/src/m20230309_000001_create_table_domain.rs +++ b/rust/main/agents/scraper/migration/src/m20230309_000001_create_table_domain.rs @@ -438,6 +438,14 @@ const DOMAINS: &[RawDomain] = &[ is_test_net: false, is_deprecated: false, }, + RawDomain { + name: "stride", + token: "STRD", + domain: 745, + chain_id: 745, + is_test_net: false, + is_deprecated: false, + }, RawDomain { name: "cosmostest99990", token: "OSMO", diff --git a/rust/main/config/mainnet_config.json b/rust/main/config/mainnet_config.json index dc0551f9d1..a562cd84e2 100644 --- a/rust/main/config/mainnet_config.json +++ b/rust/main/config/mainnet_config.json @@ -34,7 +34,7 @@ "interchainAccountIsm": "0xd766e7C7517f2d0D92754b2fe4aE7AdEf7bDEC3e", "interchainAccountRouter": "0x25C87e735021F72d8728438C2130b02E3141f2cb", "interchainGasPaymaster": "0x8F1E22d309baa69D398a03cc88E9b46037e988AA", - "interchainSecurityModule": "0x565C280Cdb56095Cf6BF23b5fF140180208CBa9e", + "interchainSecurityModule": "0x4e1d2cdB48A2C2912b11801Eb1F1d5007474cA43", "isTestnet": false, "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0x811808Dd29ba8B0FC6C0ec0b5537035E59745162", @@ -100,7 +100,7 @@ "interchainAccountIsm": "0x2A7574358Ec53522CE2452887661AB4c86F7d400", "interchainAccountRouter": "0x91874Dbed74925dFe6059B90385EEb90DdE0B2E6", "interchainGasPaymaster": "0x3b6044acd6767f017e99318AA6Ef93b7B06A5a22", - "interchainSecurityModule": "0x3d0BE14dFbB1Eb736303260c1724B6ea270c8Dc4", + "interchainSecurityModule": "0x50d0b0E27B8B93119618f053A623886116dd3b6d", "mailbox": "0x979Ca5202784112f4738403dBec5D0F3B9daabB9", "merkleTreeHook": "0x748040afB89B8FdBb992799808215419d36A0930", "name": "arbitrum", @@ -172,7 +172,7 @@ "interchainAccountIsm": "0x27a3233c05C1Df7c163123301D14bE9349E3Cb48", "interchainAccountRouter": "0xa82a0227e6d6db53AF4B264A852bfF91C6504a51", "interchainGasPaymaster": "0x95519ba800BBd0d34eeAE026fEc620AD978176C0", - "interchainSecurityModule": "0x6723A49c12FE37ccBD08512dd586C8471743676f", + "interchainSecurityModule": "0xbc803Da34A88E5f6B50dfc0CC9D924d9865c91C5", "mailbox": "0xFf06aFcaABaDDd1fb08371f9ccA15D73D51FeBD6", "merkleTreeHook": "0x84eea61D679F42D92145fA052C89900CBAccE95A", "name": "avalanche", @@ -245,7 +245,7 @@ "interchainAccountIsm": "0x223F7D3f27E6272266AE4B5B91Fd5C7A2d798cD8", "interchainAccountRouter": "0x4767D22117bBeeb295413000B620B93FD8522d53", "interchainGasPaymaster": "0xc3F23848Ed2e04C0c6d41bd7804fa8f89F940B94", - "interchainSecurityModule": "0xaeEf8f7D049C03181E0B5f2746CA2Db4d25C0B82", + "interchainSecurityModule": "0xB7fcb4665ace2B0d36fd92D26b4a8B516c0bFe5F", "mailbox": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", "merkleTreeHook": "0x19dc38aeae620380430C200a6E990D5Af5480117", "name": "base", @@ -316,7 +316,7 @@ "interchainAccountIsm": "0xe93f2f409ad8B5000431D234472973fe848dcBEC", "interchainAccountRouter": "0x2f4Eb04189e11Af642237Da62d163Ab714614498", "interchainGasPaymaster": "0xB3fCcD379ad66CED0c91028520C64226611A48c9", - "interchainSecurityModule": "0xbCe3469E8C270e04ea9ccd20Efdeed2b90c9d57C", + "interchainSecurityModule": "0xECa4a584E91867a72cd036DB7Db22Ad894a197B7", "mailbox": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7", "merkleTreeHook": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", "name": "blast", @@ -384,7 +384,7 @@ "interchainAccountIsm": "0x451dF8AB0936D85526D816f0b4dCaDD934A034A4", "interchainAccountRouter": "0x5C02157068a52cEcfc98EDb6115DE6134EcB4764", "interchainGasPaymaster": "0x62B7592C1B6D1E43f4630B8e37f4377097840C05", - "interchainSecurityModule": "0x26eA240CB4cABd7B75A9F17E7f4e224170270Ee3", + "interchainSecurityModule": "0x93E3e6CA295803417212421785606B1F7dDeaD8f", "mailbox": "0x8358D8291e3bEDb04804975eEa0fe9fe0fAfB147", "merkleTreeHook": "0x781bE492F1232E66990d83a9D3AC3Ec26f56DAfB", "name": "bob", @@ -450,7 +450,7 @@ "interchainAccountIsm": "0x9e22945bE593946618383B108CC5bce09eBA4C26", "interchainAccountRouter": "0x32A07c1B7a7fe8D4A0e44B0181873aB9d64C16c1", "interchainGasPaymaster": "0x78E25e7f84416e69b9339B0A6336EB6EFfF6b451", - "interchainSecurityModule": "0x0A7cE15E3cc638abC3B5FfA7Fc8F3295Ae3595D1", + "interchainSecurityModule": "0xA0506B5b12770494740A4a7cc86C9A36Dc1Fc6Dc", "mailbox": "0x2971b9Aec44bE4eb673DF1B88cDB57b96eefe8a4", "merkleTreeHook": "0xFDb9Cd5f9daAA2E4474019405A328a88E7484f26", "name": "bsc", @@ -531,7 +531,7 @@ "interchainAccountIsm": "0xB732c83aeE29596E3163Da2260710eAB67Bc0B29", "interchainAccountRouter": "0x27a6cAe33378bB6A6663b382070427A01fc9cB37", "interchainGasPaymaster": "0x571f1435613381208477ac5d6974310d88AC7cB7", - "interchainSecurityModule": "0x33BC62504248F4cb43813532067ccAEd46a5e61C", + "interchainSecurityModule": "0xa6f4835940dbA46E295076D0CD0411349C33789f", "mailbox": "0x50da3B3907A08a24fe4999F4Dcf337E8dC7954bb", "merkleTreeHook": "0x04dB778f05854f26E67e0a66b740BBbE9070D366", "name": "celo", @@ -596,7 +596,7 @@ "interchainAccountIsm": "0x4Eb82Ee35b0a1c1d776E3a3B547f9A9bA6FCC9f2", "interchainAccountRouter": "0xEF9A332Ec1fD233Bf9344A58be56ff9E104B4f60", "interchainGasPaymaster": "0x7E27456a839BFF31CA642c060a2b68414Cb6e503", - "interchainSecurityModule": "0xc1FF2bf7a4C315bE2a06941D236457EB02F93993", + "interchainSecurityModule": "0x05f6BAa16F1aCf7b19c4A09E019D856c10ab8355", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0x0054D19613f20dD72721A146ED408971a2CCA9BD", "name": "cheesechain", @@ -659,7 +659,7 @@ "from": 4842212 }, "interchainGasPaymaster": "0x9844aFFaBE17c37F791ff99ABa58B0FbB75e22AF", - "interchainSecurityModule": "0xEfFaEddFf4FFd6F8a6D714bf149b114fD34E5Fd4", + "interchainSecurityModule": "0x9A746C4BC2bE7E657A3469f0a0DAA1dE517b8514", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0xF5da68b2577EF5C0A0D98aA2a58483a68C2f232a", "name": "cyber", @@ -726,7 +726,7 @@ "from": 23783929 }, "interchainGasPaymaster": "0x9844aFFaBE17c37F791ff99ABa58B0FbB75e22AF", - "interchainSecurityModule": "0xB5e6AA45E117f9fD2a3F8e78432fCAfdB833d316", + "interchainSecurityModule": "0x168E9C1481F50E66Cc5F5E24b04eBf7071629c4E", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0xF5da68b2577EF5C0A0D98aA2a58483a68C2f232a", "name": "degenchain", @@ -839,7 +839,7 @@ "interchainAccountIsm": "0xCeafc098e5c3c7768b9229Be2FEC275862A81Abd", "interchainAccountRouter": "0xed9a722c543883FB7e07E78F3879762DE09eA7D5", "interchainGasPaymaster": "0xB30EAB08aa87138D57168D0e236850A530f49921", - "interchainSecurityModule": "0xC0737f1EA1d0aF287c2804090370b1715c593385", + "interchainSecurityModule": "0x094120BaC576aD7D88ec6893C9B220a0e64923E9", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0xC831271c1fB212012811a91Dd43e5926C1020563", "name": "endurance", @@ -910,7 +910,7 @@ "interchainAccountIsm": "0x292C614ED53DaaDBf971521bc2C652d1ca51cB47", "interchainAccountRouter": "0x5E532F7B610618eE73C2B462978e94CB1F7995Ce", "interchainGasPaymaster": "0x9e6B1022bE9BBF5aFd152483DAD9b88911bC8611", - "interchainSecurityModule": "0xFc440a9c946Bf4840623E4adf49646089361584a", + "interchainSecurityModule": "0x23d160e4474Ce011829c71Bf1bCaA40F0b5612D5", "mailbox": "0xc005dc82818d67AF737725bD4bf75435d065D239", "merkleTreeHook": "0x48e6c30B97748d1e2e03bf3e9FbE3890ca5f8CCA", "name": "ethereum", @@ -979,7 +979,7 @@ "interchainAccountIsm": "0x7C012DCA02C42cfA3Fd7Da3B0ED7234B52AE68eF", "interchainAccountRouter": "0xbed53B5C5BCE9433f25A2A702e6df13E22d84Ae9", "interchainGasPaymaster": "0x2Fca7f6eC3d4A0408900f2BB30004d4616eE985E", - "interchainSecurityModule": "0x2bed66bbfE45f1d73928179e72B73e5eCF7B9900", + "interchainSecurityModule": "0x8B497dff421844Bb0882E0C495d0851D4461675C", "mailbox": "0x2f9DB5616fa3fAd1aB06cB2C906830BA63d135e3", "merkleTreeHook": "0x8358D8291e3bEDb04804975eEa0fe9fe0fAfB147", "name": "fraxtal", @@ -1047,7 +1047,7 @@ "interchainAccountIsm": "0x9629c28990F11c31735765A6FD59E1E1bC197DbD", "interchainAccountRouter": "0x2351FBe24C1212F253b7a300ff0cBCFd97952a19", "interchainGasPaymaster": "0xFB9e40D811Cea562cc8a322b029eF2BDcC3ef6ed", - "interchainSecurityModule": "0x1E12bdb6a33442102FEF2ab766b93c863653B4bE", + "interchainSecurityModule": "0x69b33D67B9C51D45E23d22E727FF186DD6298ECA", "mailbox": "0x3071D4DA6020C956Fe15Bfd0a9Ca8D4574f16696", "merkleTreeHook": "0xfBc08389224d23b79cb21cDc16c5d42F0ad0F57f", "name": "fusemainnet", @@ -1121,7 +1121,7 @@ "interchainAccountIsm": "0x07E2062A1bC66a2C1d05cb5C3870a4AF86e0056E", "interchainAccountRouter": "0xBE70Ab882D1F7E37e04a70CDd9Ec23b37a234064", "interchainGasPaymaster": "0xDd260B99d302f0A3fF885728c086f729c06f227f", - "interchainSecurityModule": "0x996Be332325DA49Ea590A9772a515d62dD90C74c", + "interchainSecurityModule": "0x00533a5F14B3a0632C86f99E4e20a10b73C4AE0D", "mailbox": "0xaD09d78f4c6b9dA2Ae82b1D34107802d380Bb74f", "merkleTreeHook": "0x2684C6F89E901987E1FdB7649dC5Be0c57C61645", "name": "gnosis", @@ -1184,7 +1184,7 @@ "domainId": 2525, "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", - "fallbackRoutingHook": "0xA376b27212D608324808923Add679A2c9FAFe9Da", + "fallbackRoutingHook": "0xf8dba46ff9d8ef650052c89ca2df793fabc375f9", "gasCurrencyCoinGeckoId": "injective-protocol", "index": { "from": 37 @@ -1192,7 +1192,7 @@ "interchainAccountIsm": "0x708E002637792FDC031E6B62f23DD60014AC976a", "interchainAccountRouter": "0xfB8cea1c7F45608Da30655b50bbF355D123A4358", "interchainGasPaymaster": "0x19dc38aeae620380430C200a6E990D5Af5480117", - "interchainSecurityModule": "0x1Bff27a1c1319b0704348895b67426a4Eb1b629e", + "interchainSecurityModule": "0xB8F85B879775adF156Dd4AFa43e97DeB880d99D4", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0x0972954923a1e2b2aAb04Fa0c4a0797e5989Cd65", "name": "inevm", @@ -1229,7 +1229,14 @@ }, "injective": { "bech32Prefix": "inj", - "blockExplorers": [], + "blockExplorers": [ + { + "apiUrl": "https://www.mintscan.io/injective", + "family": "other", + "name": "Mintscan", + "url": "https://www.mintscan.io/injective" + } + ], "blocks": { "confirmations": 1, "estimateBlockTime": 1, @@ -1313,7 +1320,7 @@ "from": 14616307 }, "interchainGasPaymaster": "0x9844aFFaBE17c37F791ff99ABa58B0FbB75e22AF", - "interchainSecurityModule": "0xcaE599dD2142CD8F09a68bAC89b4d809A038d2E9", + "interchainSecurityModule": "0xE2968dAb74541184Ad95651b1e5Cf34Ab1bBEc97", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0xF5da68b2577EF5C0A0D98aA2a58483a68C2f232a", "name": "kroma", @@ -1386,7 +1393,7 @@ "interchainAccountIsm": "0xdcA646C56E7768DD11654956adE24bfFf9Ba4893", "interchainAccountRouter": "0xD59dA396F162Ed93a41252Cebb8d5DD4F093238C", "interchainGasPaymaster": "0x8105a095368f1a184CceA86cCe21318B5Ee5BE28", - "interchainSecurityModule": "0x916e612358dA3F7E8e19f51ba2Cf7af3285a6793", + "interchainSecurityModule": "0x0EEF1e64646EE01DeED4850074Cd4B97C0A630a9", "mailbox": "0x02d16BC51af6BfD153d67CA61754cF912E82C4d9", "merkleTreeHook": "0xC077A0Cc408173349b1c9870C667B40FE3C01dd7", "name": "linea", @@ -1457,7 +1464,7 @@ "from": 4195553 }, "interchainGasPaymaster": "0x9844aFFaBE17c37F791ff99ABa58B0FbB75e22AF", - "interchainSecurityModule": "0xa660FA2047105E0341b356393218586ED2191d14", + "interchainSecurityModule": "0x63Bb509b9CA644609B15Ea55E56f0Acbbb9dB02E", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0xF5da68b2577EF5C0A0D98aA2a58483a68C2f232a", "name": "lisk", @@ -1521,7 +1528,7 @@ "from": 3088760 }, "interchainGasPaymaster": "0x441a01Fca2eD731C0Fc4633998332f9FEDB17575", - "interchainSecurityModule": "0x391BD8dD2709F77d39dab4fB3bf4BCAfd5EC3248", + "interchainSecurityModule": "0x609ad94304896607A6D81DB00d882245045B79da", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0x062200d92dF6bB7bA89Ce4D6800110450f94784e", "name": "lukso", @@ -1595,7 +1602,7 @@ "interchainAccountIsm": "0x8Ea50255C282F89d1A14ad3F159437EE5EF0507f", "interchainAccountRouter": "0x693A4cE39d99e46B04cb562329e3F0141cA17331", "interchainGasPaymaster": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", - "interchainSecurityModule": "0xBcB96842301c659B575c40C3f93F7311D09c93d9", + "interchainSecurityModule": "0xC012e8E3cBeB6295E1E4837FBA5DB8E077EBc549", "isTestnet": false, "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x149db7afD694722747035d5AEC7007ccb6F8f112", @@ -1665,7 +1672,7 @@ "interchainAccountIsm": "0xe039DA3A0071BEd087A12660D7b03cf669c7776E", "interchainAccountRouter": "0x45285463352c53a481e882cD5E2AF2E25BBdAd0D", "interchainGasPaymaster": "0x8105a095368f1a184CceA86cCe21318B5Ee5BE28", - "interchainSecurityModule": "0xe542Ed13B5782aF0AFAe7f232bB867FB0208Ab7B", + "interchainSecurityModule": "0x8722328A5Ed815965F9B5eBAA21d04f0F9BFDd35", "mailbox": "0x398633D19f4371e1DB5a8EFE90468eB70B1176AA", "merkleTreeHook": "0x5332D1AC0A626D265298c14ff681c0A8D28dB86d", "name": "mantle", @@ -1727,7 +1734,7 @@ "from": 13523607 }, "interchainGasPaymaster": "0x9844aFFaBE17c37F791ff99ABa58B0FbB75e22AF", - "interchainSecurityModule": "0x5725a9Bf3bdaF104Ce1A0098308DD974F6395365", + "interchainSecurityModule": "0xE8176Fc70f129255aA83d3db242C2246Ad77Af7D", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0xF5da68b2577EF5C0A0D98aA2a58483a68C2f232a", "name": "merlin", @@ -1794,7 +1801,7 @@ "from": 17966274 }, "interchainGasPaymaster": "0x9844aFFaBE17c37F791ff99ABa58B0FbB75e22AF", - "interchainSecurityModule": "0xfd810678075502F76A6ef79cedffe58cff6Cc410", + "interchainSecurityModule": "0xA9309228762699D5c81A4b0BAfd06Da21589746b", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0xF5da68b2577EF5C0A0D98aA2a58483a68C2f232a", "name": "metis", @@ -1859,7 +1866,7 @@ "from": 3752032 }, "interchainGasPaymaster": "0x9844aFFaBE17c37F791ff99ABa58B0FbB75e22AF", - "interchainSecurityModule": "0xfd810678075502F76A6ef79cedffe58cff6Cc410", + "interchainSecurityModule": "0xA9309228762699D5c81A4b0BAfd06Da21589746b", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0xF5da68b2577EF5C0A0D98aA2a58483a68C2f232a", "name": "mint", @@ -1926,7 +1933,7 @@ "interchainAccountIsm": "0xa377b8269e0A47cdd2fD5AAeAe860b45623c6d82", "interchainAccountRouter": "0x6e1B9f776bd415d7cC3C7458A5f0d801016918f8", "interchainGasPaymaster": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", - "interchainSecurityModule": "0x1ab6985E0e15d293e71d01510fa6B57311C69718", + "interchainSecurityModule": "0x0777dFffcEd18EE416e35401E0e5e0413b7D43be", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0xE2ee936bEa8e42671c400aC96dE198E06F2bA2A6", "name": "mode", @@ -1994,7 +2001,7 @@ "interchainAccountIsm": "0x79b3730CE3685f65802aF1771319992bA960EB9D", "interchainAccountRouter": "0xc4482f66191754a8629D35289043C4EB0285F10E", "interchainGasPaymaster": "0x14760E32C0746094cF14D97124865BC7F0F7368F", - "interchainSecurityModule": "0x105215D46F45a88B3793B798Fb87AF231Ad9f611", + "interchainSecurityModule": "0xad1Ad827035eDe500aFd0ff122c53f6eA607Eb5C", "mailbox": "0x094d03E751f49908080EFf000Dd6FD177fd44CC3", "merkleTreeHook": "0x87403b85f6f316e7ba91ba1fa6C3Fb7dD4095547", "name": "moonbeam", @@ -2136,7 +2143,7 @@ "interchainAccountIsm": "0x2c46BF14641d00549ECa4779BF5CBf91602C1DEd", "interchainAccountRouter": "0x03D6cC17d45E9EA27ED757A8214d1F07F7D901aD", "interchainGasPaymaster": "0xD8A76C4D91fCbB7Cc8eA795DFDF870E48368995C", - "interchainSecurityModule": "0x1E491c93Ab56A298bBC34B4Fca8A4cE68E8d540a", + "interchainSecurityModule": "0x3878aB31B2426A92E8a1E0AE758d848879F7F5E8", "mailbox": "0xd4C1905BB1D26BC93DAC913e13CaCC278CdCC80D", "merkleTreeHook": "0x68eE9bec9B4dbB61f69D9D293Ae26a5AACb2e28f", "name": "optimism", @@ -2271,7 +2278,7 @@ "interchainAccountIsm": "0xBAC4529cdfE7CCe9E858BF706e41F8Ed096C1BAd", "interchainAccountRouter": "0xF163949AD9F88977ebF649D0461398Ca752E64B9", "interchainGasPaymaster": "0x0071740Bf129b05C4684abfbBeD248D80971cce2", - "interchainSecurityModule": "0x0D408EF040ca0E7a75f03c41Aa46cAc904770D45", + "interchainSecurityModule": "0x9fFC02BfB5C7260C985b005C0cF40d7EC601aac2", "mailbox": "0x5d934f4e2f797775e53561bB72aca21ba36B96BB", "merkleTreeHook": "0x73FbD25c3e817DC4B4Cd9d00eff6D83dcde2DfF6", "name": "polygon", @@ -2348,7 +2355,7 @@ "interchainAccountIsm": "0xc1198e241DAe48BF5AEDE5DCE49Fe4A6064cF7a7", "interchainAccountRouter": "0x20a0A32a110362920597F72974E1E0d7e25cA20a", "interchainGasPaymaster": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", - "interchainSecurityModule": "0x5C622f40365F57D06b5e7132B1483fcbc9566560", + "interchainSecurityModule": "0xc6c475184F197FA65f233dFc22FA6bD4cE48B4fE", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x149db7afD694722747035d5AEC7007ccb6F8f112", "name": "polygonzkevm", @@ -2416,7 +2423,7 @@ "from": 32018468 }, "interchainGasPaymaster": "0x9844aFFaBE17c37F791ff99ABa58B0FbB75e22AF", - "interchainSecurityModule": "0x336306ADB3c510A318107c01D109D2072c7abB6B", + "interchainSecurityModule": "0x70e8beCE806914959c1B5D8F75d2217058D31437", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0xF5da68b2577EF5C0A0D98aA2a58483a68C2f232a", "name": "proofofplay", @@ -2480,7 +2487,7 @@ "from": 363159 }, "interchainGasPaymaster": "0x3071D4DA6020C956Fe15Bfd0a9Ca8D4574f16696", - "interchainSecurityModule": "0x06567d07b49ad922A821A641F1C962569a255dbB", + "interchainSecurityModule": "0x43346a54445BBdf8241062904E8A13AA62842a02", "mailbox": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", "merkleTreeHook": "0x55E4F0bc6b7Bb493D50839A8592e7ad8d5e93cf7", "name": "real", @@ -2547,7 +2554,7 @@ "interchainAccountIsm": "0x5DA60220C5dDe35b7aE91c042ff5979047FA0785", "interchainAccountRouter": "0x7a4d31a686A36285d68e14EDD53631417eB19603", "interchainGasPaymaster": "0x2Fa570E83009eaEef3a1cbd496a9a30F05266634", - "interchainSecurityModule": "0x5a3bB91853CfDaDb1bC95D2E061B50d433fbe3E8", + "interchainSecurityModule": "0xd8b6B632526834D8192860e6B6CE47165Fd02a42", "mailbox": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", "merkleTreeHook": "0x8F1E22d309baa69D398a03cc88E9b46037e988AA", "name": "redstone", @@ -2609,7 +2616,7 @@ "from": 937117 }, "interchainGasPaymaster": "0x9844aFFaBE17c37F791ff99ABa58B0FbB75e22AF", - "interchainSecurityModule": "0xA76F4620ac1e97d273B2C9Ca71805c8afD792098", + "interchainSecurityModule": "0x6A2748201F66647ad6D164CB3340A893881A4bb2", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0xF5da68b2577EF5C0A0D98aA2a58483a68C2f232a", "name": "sanko", @@ -2677,7 +2684,7 @@ "interchainAccountIsm": "0x32af5Df81fEd5E26119F6640FBB13f3d63a94CDe", "interchainAccountRouter": "0x0B48a744698ba8dFa514742dFEB6728f52fD66f7", "interchainGasPaymaster": "0xBF12ef4B9f307463D3FB59c3604F294dDCe287E2", - "interchainSecurityModule": "0x37e17723b665A9F95F4aecEEc40D9eF39624764e", + "interchainSecurityModule": "0xAd1a987BfE0D6fbD92089628daC7C7e4bA9a6AAF", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0x6119E37Bd66406A1Db74920aC79C15fB8411Ba76", "name": "scroll", @@ -2745,7 +2752,7 @@ "interchainAccountIsm": "0xf35dc7B9eE4Ebf0cd3546Bd6EE3b403dE2b9F5D6", "interchainAccountRouter": "0xBcaedE97a98573A88242B3b0CB0A255F3f90d4d5", "interchainGasPaymaster": "0xFC62DeF1f08793aBf0E67f69257c6be258194F72", - "interchainSecurityModule": "0x26184898fA27D3471B50BD956AB5b3E9Aa14763C", + "interchainSecurityModule": "0x494028EA206642e4c60Ec3d12e96B4549E5e1800", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0xca1b69fA4c4a7c7fD839bC50867c589592bcfe49", "name": "sei", @@ -2860,7 +2867,7 @@ "interchainAccountIsm": "0xAE557e108b3336130370aC74836f1356B4b30Cf2", "interchainAccountRouter": "0x1F8CF09F060A2AE962c0Bb1F92e209a1E7b0E10B", "interchainGasPaymaster": "0x273Bc6b01D9E88c064b6E5e409BdF998246AEF42", - "interchainSecurityModule": "0x478D0e5d221C8d7CA1A6353315fac6FA88Ff1B97", + "interchainSecurityModule": "0xC93F2796A17Ee4580c039aeB7b0c923b10ce79C2", "mailbox": "0x28EFBCadA00A7ed6772b3666F3898d276e88CAe3", "merkleTreeHook": "0x6A55822cf11f9fcBc4c75BC2638AfE8Eb942cAdd", "name": "taiko", @@ -2922,7 +2929,7 @@ "from": 1678063 }, "interchainGasPaymaster": "0x9844aFFaBE17c37F791ff99ABa58B0FbB75e22AF", - "interchainSecurityModule": "0x336306ADB3c510A318107c01D109D2072c7abB6B", + "interchainSecurityModule": "0x70e8beCE806914959c1B5D8F75d2217058D31437", "isTestnet": false, "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0xF5da68b2577EF5C0A0D98aA2a58483a68C2f232a", @@ -2990,7 +2997,7 @@ "interchainAccountIsm": "0x551BbEc45FD665a8C95ca8731CbC32b7653Bc59B", "interchainAccountRouter": "0xc11f8Cf2343d3788405582F65B8af6A4F7a6FfC8", "interchainGasPaymaster": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", - "interchainSecurityModule": "0x5542503cBde841c336Afef014FE80b0F1c9d716d", + "interchainSecurityModule": "0x3465AccC39AE5e6C344184013a57cDCe546834d6", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0x149db7afD694722747035d5AEC7007ccb6F8f112", "name": "viction", @@ -3058,7 +3065,7 @@ "interchainAccountIsm": "0xCB9f90EE5d83Ea52ABd922BD70898f0155D54798", "interchainAccountRouter": "0x473884010F0C1742DA8Ad01E7E295624B931076b", "interchainGasPaymaster": "0x7E27456a839BFF31CA642c060a2b68414Cb6e503", - "interchainSecurityModule": "0xc1FF2bf7a4C315bE2a06941D236457EB02F93993", + "interchainSecurityModule": "0x05f6BAa16F1aCf7b19c4A09E019D856c10ab8355", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0x0054D19613f20dD72721A146ED408971a2CCA9BD", "name": "worldchain", @@ -3120,7 +3127,7 @@ "from": 24395308 }, "interchainGasPaymaster": "0x9844aFFaBE17c37F791ff99ABa58B0FbB75e22AF", - "interchainSecurityModule": "0xC630ef37e03534721557026FeC397Ec05f85584C", + "interchainSecurityModule": "0x4886ed96bcdba2ad85Bf518C3171C39e256ac840", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0xF5da68b2577EF5C0A0D98aA2a58483a68C2f232a", "name": "xai", @@ -3188,7 +3195,7 @@ "interchainAccountIsm": "0x29B37088724B745C0ABcE591449Cf042772160C2", "interchainAccountRouter": "0x03cF708E42C89623bd83B281A56935cB562b9258", "interchainGasPaymaster": "0x7E27456a839BFF31CA642c060a2b68414Cb6e503", - "interchainSecurityModule": "0x0811C6250965E7Ba1A872A12249B1b2300d85CFD", + "interchainSecurityModule": "0x59B0ec92522F164b72c9BE473382197c564B92dc", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0x0054D19613f20dD72721A146ED408971a2CCA9BD", "name": "xlayer", @@ -3256,7 +3263,7 @@ "interchainAccountIsm": "0x2b6d3F7d28B5EC8C3C028fBCAdcf774D9709Dd29", "interchainAccountRouter": "0x3AdCBc94ab8C48EC52D06dc65Bb787fD1981E3d5", "interchainGasPaymaster": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", - "interchainSecurityModule": "0x858a077945bCC4afA40DaE5a75faB9237e899b30", + "interchainSecurityModule": "0xd32353Ae5719ac4f8f24AeD81A2A6898d2632D26", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0xE2ee936bEa8e42671c400aC96dE198E06F2bA2A6", "name": "zetachain", @@ -3322,7 +3329,7 @@ "from": 1511458 }, "interchainGasPaymaster": "0x03cF708E42C89623bd83B281A56935cB562b9258", - "interchainSecurityModule": "0xb04DE6Cbc6E258E8f2D2B00EBdcD2Ea901791EAA", + "interchainSecurityModule": "0xFFec270FE3D0e3B9348B3664BE73A5d4906BA620", "mailbox": "0xc2FbB9411186AB3b1a6AFCCA702D1a80B48b197c", "merkleTreeHook": "0x4C97D35c668EE5194a13c8DE8Afc18cce40C9F28", "name": "zircuit", @@ -3395,7 +3402,7 @@ "interchainAccountIsm": "0xb2674E213019972f937CCFc5e23BF963D915809e", "interchainAccountRouter": "0x11b76D93a9D39Eb51F54eBf5566308640cDe882b", "interchainGasPaymaster": "0x18B0688990720103dB63559a3563f7E8d0f63EDb", - "interchainSecurityModule": "0xDede6e50E548460FB6a939320F707214CFfC701C", + "interchainSecurityModule": "0xED5fD1715A0885a3C7B908BAd5c8C64Ba5166265", "mailbox": "0xF5da68b2577EF5C0A0D98aA2a58483a68C2f232a", "merkleTreeHook": "0x886BB0f329781b98f98FDeb1ce7a8957F2d43B9F", "name": "zoramainnet", @@ -3466,7 +3473,7 @@ "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0xc401e251CCa7A364114504A994D6fC7cb1c243AB", "interchainGasPaymaster": "0x4E55aDA3ef1942049EA43E904EB01F4A0a9c39bd", - "interchainSecurityModule": "0x2b19CEB3ef3B1D7428b0788d44401E44E381B62B", + "interchainSecurityModule": "0x8Ad4d573D7EafC4Ca58f1dB704B8Db804814D674", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x441a01Fca2eD731C0Fc4633998332f9FEDB17575", "pausableHook": "0x5Ed813B8b41f25c8002B01A72bbDBe6A0232Fe27", @@ -3533,7 +3540,7 @@ "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0xc401e251CCa7A364114504A994D6fC7cb1c243AB", "interchainGasPaymaster": "0x4E55aDA3ef1942049EA43E904EB01F4A0a9c39bd", - "interchainSecurityModule": "0x8Ad4d573D7EafC4Ca58f1dB704B8Db804814D674", + "interchainSecurityModule": "0xa18979d2e5b8A64f62E0f9e9523d28E934F1104c", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x441a01Fca2eD731C0Fc4633998332f9FEDB17575", "pausableHook": "0x5Ed813B8b41f25c8002B01A72bbDBe6A0232Fe27", @@ -3603,7 +3610,7 @@ "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0xc401e251CCa7A364114504A994D6fC7cb1c243AB", "interchainGasPaymaster": "0x4E55aDA3ef1942049EA43E904EB01F4A0a9c39bd", - "interchainSecurityModule": "0x2b19CEB3ef3B1D7428b0788d44401E44E381B62B", + "interchainSecurityModule": "0x8Ad4d573D7EafC4Ca58f1dB704B8Db804814D674", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x441a01Fca2eD731C0Fc4633998332f9FEDB17575", "pausableHook": "0x5Ed813B8b41f25c8002B01A72bbDBe6A0232Fe27", @@ -3679,7 +3686,7 @@ "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0xc401e251CCa7A364114504A994D6fC7cb1c243AB", "interchainGasPaymaster": "0x4E55aDA3ef1942049EA43E904EB01F4A0a9c39bd", - "interchainSecurityModule": "0x2b19CEB3ef3B1D7428b0788d44401E44E381B62B", + "interchainSecurityModule": "0x8Ad4d573D7EafC4Ca58f1dB704B8Db804814D674", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x441a01Fca2eD731C0Fc4633998332f9FEDB17575", "pausableHook": "0x5Ed813B8b41f25c8002B01A72bbDBe6A0232Fe27", @@ -3743,7 +3750,7 @@ "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0xc401e251CCa7A364114504A994D6fC7cb1c243AB", "interchainGasPaymaster": "0x4E55aDA3ef1942049EA43E904EB01F4A0a9c39bd", - "interchainSecurityModule": "0x2b19CEB3ef3B1D7428b0788d44401E44E381B62B", + "interchainSecurityModule": "0x8Ad4d573D7EafC4Ca58f1dB704B8Db804814D674", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x441a01Fca2eD731C0Fc4633998332f9FEDB17575", "pausableHook": "0x5Ed813B8b41f25c8002B01A72bbDBe6A0232Fe27", @@ -3816,7 +3823,7 @@ "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0xc401e251CCa7A364114504A994D6fC7cb1c243AB", "interchainGasPaymaster": "0x4E55aDA3ef1942049EA43E904EB01F4A0a9c39bd", - "interchainSecurityModule": "0x2b19CEB3ef3B1D7428b0788d44401E44E381B62B", + "interchainSecurityModule": "0x8Ad4d573D7EafC4Ca58f1dB704B8Db804814D674", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x441a01Fca2eD731C0Fc4633998332f9FEDB17575", "pausableHook": "0x5Ed813B8b41f25c8002B01A72bbDBe6A0232Fe27", @@ -3884,7 +3891,7 @@ "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0xc401e251CCa7A364114504A994D6fC7cb1c243AB", "interchainGasPaymaster": "0x4E55aDA3ef1942049EA43E904EB01F4A0a9c39bd", - "interchainSecurityModule": "0x2b19CEB3ef3B1D7428b0788d44401E44E381B62B", + "interchainSecurityModule": "0x8Ad4d573D7EafC4Ca58f1dB704B8Db804814D674", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x441a01Fca2eD731C0Fc4633998332f9FEDB17575", "pausableHook": "0x5Ed813B8b41f25c8002B01A72bbDBe6A0232Fe27", @@ -3947,7 +3954,7 @@ "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0xc401e251CCa7A364114504A994D6fC7cb1c243AB", "interchainGasPaymaster": "0x4E55aDA3ef1942049EA43E904EB01F4A0a9c39bd", - "interchainSecurityModule": "0x12C8cfA5B97Df96AaB2795a4186675dA49938968", + "interchainSecurityModule": "0xf08b7F859966ed27286Fe7d924A42b40e2DB80Bd", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x441a01Fca2eD731C0Fc4633998332f9FEDB17575", "pausableHook": "0x5Ed813B8b41f25c8002B01A72bbDBe6A0232Fe27", @@ -4017,7 +4024,7 @@ "interchainAccountIsm": "0xcd9D3744512F07AE844c40E27912092d7c503565", "interchainAccountRouter": "0x92cdbF0Ccdf8E93467FA858fb986fa650A02f2A8", "interchainGasPaymaster": "0xb58257cc81E47EC72fD38aE16297048de23163b4", - "interchainSecurityModule": "0xA16954f5040ba7fE2286569aD107a22a46FffDC6", + "interchainSecurityModule": "0xcC5C35a6214982d9018B95e9684D5b4dA626237e", "mailbox": "0x7f50C5776722630a0024fAE05fDe8b47571D7B39", "merkleTreeHook": "0xCC3D1659D50461d27a2F025dDb2c9B06B584B7e1", "pausableHook": "0x4E55aDA3ef1942049EA43E904EB01F4A0a9c39bd", @@ -4077,7 +4084,7 @@ "interchainAccountIsm": "0xc23BaF5Eb5848D19701BbE7f139645e6bd58a319", "interchainAccountRouter": "0x7c58Cadcc2b60ACF794eE1843488d6f5703f76BE", "interchainGasPaymaster": "0xb4fc9B5fD57499Ef6FfF3995728a55F7A618ef86", - "interchainSecurityModule": "0x128432015A0E08d490e0FD6b4dE4EAbe30f617a3", + "interchainSecurityModule": "0x50B5Edd94A1C7ad18Fa2CA667A30Dc051a695aEe", "mailbox": "0xb129828B9EDa48192D0B2db35D0E40dCF51B3594", "merkleTreeHook": "0x3E969bA938E6A993eeCD6F65b0dd8712B07dFe59", "pausableHook": "0x6Fb36672365C7c797028C400A61c58c0ECc53cD2", @@ -4149,7 +4156,7 @@ "interchainAccountIsm": "0x783EC5e105234a570eB90f314284E5dBe53bdd90", "interchainAccountRouter": "0xc5D6aCaafBCcEC6D7fD7d92F4509befce641c563", "interchainGasPaymaster": "0xe8d5590F2e969F9d21f0132f2b596273f8a03Ef2", - "interchainSecurityModule": "0xe274f228f87c0D72241CF9aAC15d91e3630aE6f6", + "interchainSecurityModule": "0x58D6fb4aADd3ae83ec529d3d0f42Ae904207a336", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0xDab56C5A1EffFdd23f6BD1243E457B1575984Bc6", "pausableHook": "0x73db9c7430548f399e335f3424e8d56080e9010c", @@ -4212,7 +4219,7 @@ "interchainAccountIsm": "0x9eaaC366BFD70430cFee6E70265fefFf1CfC9E47", "interchainAccountRouter": "0xbb0AE51BCa526cF313b6a95BfaB020794af6C394", "interchainGasPaymaster": "0xe8d5590F2e969F9d21f0132f2b596273f8a03Ef2", - "interchainSecurityModule": "0xe274f228f87c0D72241CF9aAC15d91e3630aE6f6", + "interchainSecurityModule": "0x58D6fb4aADd3ae83ec529d3d0f42Ae904207a336", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0xDab56C5A1EffFdd23f6BD1243E457B1575984Bc6", "pausableHook": "0x73db9c7430548f399e335f3424e8d56080e9010c", @@ -4282,7 +4289,7 @@ "interchainAccountIsm": "0xeE8C0E1EeBfFCC451a013336386eA53E42a44451", "interchainAccountRouter": "0x9eb56085DdbDA60aDf7d2B533AFeD90e38fC9666", "interchainGasPaymaster": "0xe8d5590F2e969F9d21f0132f2b596273f8a03Ef2", - "interchainSecurityModule": "0xe274f228f87c0D72241CF9aAC15d91e3630aE6f6", + "interchainSecurityModule": "0x58D6fb4aADd3ae83ec529d3d0f42Ae904207a336", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0xDab56C5A1EffFdd23f6BD1243E457B1575984Bc6", "pausableHook": "0x73db9c7430548f399e335f3424e8d56080e9010c", @@ -4353,7 +4360,7 @@ "interchainAccountIsm": "0x25EAC2007b0D40E3f0AF112FD346412321038719", "interchainAccountRouter": "0xfF26696DcDb6BbFD27e959b847D4f1399D5BcF64", "interchainGasPaymaster": "0x9024A3902B542C87a5C4A2b3e15d60B2f087Dc3E", - "interchainSecurityModule": "0x8750ac24C3bE28b406fBCD664AbabF676d3aCB29", + "interchainSecurityModule": "0x9dccF81bB9f419425b0a6584E8800556B92209Cc", "mailbox": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7", "merkleTreeHook": "0x9c44E6b8F0dB517C2c3a0478caaC5349b614F912", "pausableHook": "0x53e912b41125d6094590a7DBEf1360d3d56EEa19", @@ -4421,7 +4428,7 @@ "interchainAccountIsm": "0x99fEFc1119E86Ee0153eb887cF8E8ab2d92A16e8", "interchainAccountRouter": "0xbB88a31E4b709b645c06825c0E0b5CAC906d97DE", "interchainGasPaymaster": "0xe8d5590F2e969F9d21f0132f2b596273f8a03Ef2", - "interchainSecurityModule": "0xe274f228f87c0D72241CF9aAC15d91e3630aE6f6", + "interchainSecurityModule": "0x58D6fb4aADd3ae83ec529d3d0f42Ae904207a336", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0xDab56C5A1EffFdd23f6BD1243E457B1575984Bc6", "pausableHook": "0x73db9c7430548f399e335f3424e8d56080e9010c", @@ -4487,7 +4494,7 @@ "interchainAccountIsm": "0xFB9e40D811Cea562cc8a322b029eF2BDcC3ef6ed", "interchainAccountRouter": "0xeE8C0E1EeBfFCC451a013336386eA53E42a44451", "interchainGasPaymaster": "0x148CF67B8A242c1360bb2C93fCe203EC4d4f9B56", - "interchainSecurityModule": "0x01031eCa87184c84938016E1444150472Da35b3a", + "interchainSecurityModule": "0x81EC949b07e033b0346E60C2098464d115F0a997", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x53e912b41125d6094590a7DBEf1360d3d56EEa19", "pausableHook": "0x2d5918c3602F17937Ff982F7Bb7110774D3A24AD", @@ -4558,7 +4565,7 @@ "interchainAccountIsm": "0x9eb56085DdbDA60aDf7d2B533AFeD90e38fC9666", "interchainAccountRouter": "0x83475ca5bEB2Eaa59A2FF48a0544ebaa4a32c2de", "interchainGasPaymaster": "0x9024A3902B542C87a5C4A2b3e15d60B2f087Dc3E", - "interchainSecurityModule": "0x8750ac24C3bE28b406fBCD664AbabF676d3aCB29", + "interchainSecurityModule": "0x9dccF81bB9f419425b0a6584E8800556B92209Cc", "mailbox": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7", "merkleTreeHook": "0x9c44E6b8F0dB517C2c3a0478caaC5349b614F912", "pausableHook": "0x53e912b41125d6094590a7DBEf1360d3d56EEa19", @@ -4619,7 +4626,7 @@ "interchainAccountIsm": "0x61374178e45F65fF9D6252d017Cd580FC60B7654", "interchainAccountRouter": "0x783EC5e105234a570eB90f314284E5dBe53bdd90", "interchainGasPaymaster": "0xE56Da9D48E698eB70F56aeCC0BC25Ff1710EEA76", - "interchainSecurityModule": "0xE89fF24e1979F6AbD6b71b733cc62d1289d193c4", + "interchainSecurityModule": "0x9d1481A1fc2515aeE0Bf4eEeDDB75893DfAF0752", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x4757Bdd68Bba8a6d901cEC82E61E184fF2986918", "pausableHook": "0x9024A3902B542C87a5C4A2b3e15d60B2f087Dc3E", @@ -4683,7 +4690,7 @@ "interchainAccountIsm": "0xFB9e40D811Cea562cc8a322b029eF2BDcC3ef6ed", "interchainAccountRouter": "0xeE8C0E1EeBfFCC451a013336386eA53E42a44451", "interchainGasPaymaster": "0x168DFF0Ad2b180F3801883Fe5Ae56d7E7d91D5f4", - "interchainSecurityModule": "0xFF16628ff85F145Ce6d1712014D46DcFB52da75A", + "interchainSecurityModule": "0x8f93c32C19986B891beA2bF317686Aa2336b3854", "mailbox": "0x02d16BC51af6BfD153d67CA61754cF912E82C4d9", "merkleTreeHook": "0x148CF67B8A242c1360bb2C93fCe203EC4d4f9B56", "pausableHook": "0xb129828B9EDa48192D0B2db35D0E40dCF51B3594", @@ -4748,7 +4755,7 @@ "interchainAccountIsm": "0xb5668713E9BA8bC96f97D691663E70b54CE90b0A", "interchainAccountRouter": "0xc5068BB6803ADbe5600DE5189fe27A4dAcE31170", "interchainGasPaymaster": "0xA1Df6B70044029a2D1eDDC50EfDE2813e478140a", - "interchainSecurityModule": "0x729E790E70902429873c23BaA73eE39aCEEfc461", + "interchainSecurityModule": "0x01180E7B92A4fEA1e250C92E32A02Df1cA058Fe0", "mailbox": "0x783EC5e105234a570eB90f314284E5dBe53bdd90", "merkleTreeHook": "0x25d668D37f20E6f396cB5DF1DFf5A3f2F568e707", "pausableHook": "0xC9ab9Dc82F05eA118F266611f4c474529d43b599", @@ -4773,6 +4780,69 @@ "transactionOverrides": { "gasPrice": 100000000 } + }, + "stride": { + "bech32Prefix": "stride", + "blockExplorers": [ + { + "apiUrl": "https://www.mintscan.io/stride", + "family": "other", + "name": "Mintscan", + "url": "https://www.mintscan.io/stride" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 5, + "reorgPeriod": 1 + }, + "chainId": "stride-1", + "deployer": { + "name": "Stride Labs", + "url": "https://www.stride.zone" + }, + "displayName": "Stride", + "domainId": 745, + "gasCurrencyCoinGeckoId": "stride", + "grpcUrls": [ + { + "http": "https://stride-grpc.publicnode.com:443" + } + ], + "isTestnet": false, + "name": "stride", + "nativeToken": { + "decimals": 6, + "denom": "ustrd", + "name": "Stride", + "symbol": "STRD" + }, + "protocol": "cosmos", + "restUrls": [ + { + "http": "https://stride-api.polkachu.com" + } + ], + "rpcUrls": [ + { + "http": "https://stride-rpc.polkachu.com" + } + ], + "slip44": 118, + "interchainGasPaymaster": "0x0000000000000000000000000000000000000000000000000000000000000000", + "mailbox": "0x89945750e089d84581f194e1947a58480b335f18386ad4f761f05feebf5e2454", + "merkleTreeHook": "0x7ab4a8c3ba5371e34cd8d5dc584e0d924504fc21c3cbf41c3f64d436176bf007", + "validatorAnnounce": "0xf57d954bf3ddb5f1032a0e020a99e931215cf83ceb4de987c781488065aaae0d", + "gasPrice": { + "denom": "ustrd", + "amount": "0.005" + }, + "canonicalAsset": "ustrd", + "contractAddressBytes": 32, + "index": { + "from": 9152000, + "chunk": 5 + } } }, "defaultRpcConsensusType": "fallback" diff --git a/typescript/infra/config/environments/mainnet3/agent.ts b/typescript/infra/config/environments/mainnet3/agent.ts index 6b6f2bae5d..fa21db977e 100644 --- a/typescript/infra/config/environments/mainnet3/agent.ts +++ b/typescript/infra/config/environments/mainnet3/agent.ts @@ -115,6 +115,7 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig< sei: true, shibarium: true, solanamainnet: true, + stride: false, superposition: true, taiko: true, tangle: true, @@ -189,6 +190,7 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig< sei: true, shibarium: true, solanamainnet: true, + stride: true, superposition: true, taiko: true, tangle: true, @@ -264,6 +266,7 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig< shibarium: true, // Cannot scrape Sealevel chains solanamainnet: false, + stride: true, superposition: true, taiko: true, tangle: true, @@ -418,7 +421,7 @@ const hyperlane: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: 'b1ff48b-20241016-183301', + tag: '42d6b50-20241021-155906', }, gasPaymentEnforcement: gasPaymentEnforcement, metricAppContexts, @@ -437,7 +440,7 @@ const hyperlane: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: '9c0c4bb-20241018-113820', + tag: '42d6b50-20241021-155906', }, resources: scraperResources, }, @@ -452,7 +455,7 @@ const releaseCandidate: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: 'b1ff48b-20241016-183301', + tag: '42d6b50-20241021-155906', }, // We're temporarily (ab)using the RC relayer as a way to increase // message throughput. diff --git a/typescript/infra/config/environments/mainnet3/funding.ts b/typescript/infra/config/environments/mainnet3/funding.ts index dafee524d4..e69298dc37 100644 --- a/typescript/infra/config/environments/mainnet3/funding.ts +++ b/typescript/infra/config/environments/mainnet3/funding.ts @@ -82,6 +82,8 @@ export const keyFunderConfig: KeyFunderConfig< scroll: '0.5', sei: '50', shibarium: '50', + // ignore non-evm chains + stride: '0', superposition: '0.05', taiko: '0.2', tangle: '2', diff --git a/typescript/infra/config/environments/mainnet3/gasPrices.json b/typescript/infra/config/environments/mainnet3/gasPrices.json index d68a6d2908..7f61122c8d 100644 --- a/typescript/infra/config/environments/mainnet3/gasPrices.json +++ b/typescript/infra/config/environments/mainnet3/gasPrices.json @@ -243,6 +243,10 @@ "amount": "0.5", "decimals": 1 }, + "stride": { + "amount": "0.005", + "decimals": 1 + }, "superposition": { "amount": "0.01", "decimals": 9 diff --git a/typescript/infra/config/environments/mainnet3/supportedChainNames.ts b/typescript/infra/config/environments/mainnet3/supportedChainNames.ts index c5eabc80cc..3e59572411 100644 --- a/typescript/infra/config/environments/mainnet3/supportedChainNames.ts +++ b/typescript/infra/config/environments/mainnet3/supportedChainNames.ts @@ -62,6 +62,7 @@ export const mainnet3SupportedChainNames = [ 'sei', 'shibarium', 'solanamainnet', + 'stride', 'superposition', 'taiko', 'tangle', diff --git a/typescript/infra/config/environments/mainnet3/tokenPrices.json b/typescript/infra/config/environments/mainnet3/tokenPrices.json index df245702ed..86c2ed731b 100644 --- a/typescript/infra/config/environments/mainnet3/tokenPrices.json +++ b/typescript/infra/config/environments/mainnet3/tokenPrices.json @@ -60,6 +60,7 @@ "sei": "0.447635", "shibarium": "0.410927", "solanamainnet": "155.35", + "stride": "0.840153", "superposition": "2629.74", "taiko": "2629.74", "tangle": "1", diff --git a/typescript/infra/src/govern/HyperlaneAppGovernor.ts b/typescript/infra/src/govern/HyperlaneAppGovernor.ts index d35a1499f0..893a25c713 100644 --- a/typescript/infra/src/govern/HyperlaneAppGovernor.ts +++ b/typescript/infra/src/govern/HyperlaneAppGovernor.ts @@ -166,17 +166,23 @@ export abstract class HyperlaneAppGovernor< // If calls are being submitted via a safe, we need to check for any safe owner changes first if (submissionType === SubmissionType.SAFE) { - const { safeSdk } = await getSafeAndService( - chain, - this.checker.multiProvider, - (multiSend as SafeMultiSend).safeAddress, - ); - const updateOwnerCalls = await updateSafeOwner(safeSdk); - callsForSubmissionType.push(...updateOwnerCalls, ...filteredCalls); - } else { - callsForSubmissionType.push(...filteredCalls); + try { + const { safeSdk } = await getSafeAndService( + chain, + this.checker.multiProvider, + (multiSend as SafeMultiSend).safeAddress, + ); + const updateOwnerCalls = await updateSafeOwner(safeSdk); + callsForSubmissionType.push(...updateOwnerCalls); + } catch (error) { + // Catch but don't throw because we want to try submitting any remaining calls + console.error(`Error updating safe owner: ${error}`); + } } + // Add the filtered calls to the calls for submission type + callsForSubmissionType.push(...filteredCalls); + if (callsForSubmissionType.length > 0) { this.printSeparator(); const confirmed = await summarizeCalls( diff --git a/typescript/sdk/src/contracts/contracts.ts b/typescript/sdk/src/contracts/contracts.ts index 90b754e8cf..5f1913a556 100644 --- a/typescript/sdk/src/contracts/contracts.ts +++ b/typescript/sdk/src/contracts/contracts.ts @@ -1,4 +1,4 @@ -import { Contract, ethers } from 'ethers'; +import { Contract } from 'ethers'; import { Ownable } from '@hyperlane-xyz/core'; import { @@ -235,31 +235,15 @@ export function appFromAddressesMapHelper( contractsMap: HyperlaneContractsMap; multiProvider: MultiProvider; } { - // Hack to accommodate non-Ethereum artifacts, while still retaining their - // presence in the addressesMap so that they are included in the list of chains - // on the MultiProvider (needed for getting metadata). A non-Ethereum-style address - // from another execution environment will cause Ethers to throw if we try to attach - // it, so we just replace it with the zero address. - const addressesMapWithEthereumizedAddresses = objMap( + // Filter out non-Ethereum chains from the addressesMap + const ethereumAddressesMap = objFilter( addressesMap, - (chain, addresses) => { - const metadata = multiProvider.getChainMetadata(chain); - if (metadata.protocol === ProtocolType.Ethereum) { - return addresses; - } - return objMap( - addresses, - (_key, _address) => ethers.constants.AddressZero, - ); - }, + (chain, _): _ is HyperlaneAddresses => + multiProvider.getProtocol(chain) === ProtocolType.Ethereum, ); - // Attaches contracts for each chain for which we have a complete set of - // addresses - const contractsMap = attachContractsMap( - addressesMapWithEthereumizedAddresses, - factories, - ); + // Attaches contracts for each Ethereum chain for which we have a complete set of addresses + const contractsMap = attachContractsMap(ethereumAddressesMap, factories); // Filters out providers for chains for which we don't have a complete set // of addresses @@ -270,6 +254,6 @@ export function appFromAddressesMapHelper( return { contractsMap: filteredContractsMap, - multiProvider: multiProvider, + multiProvider, }; } From f9f0ece7c47ed80aebc8b035db8a61b85fd3bc0d Mon Sep 17 00:00:00 2001 From: Trevor Porter Date: Wed, 23 Oct 2024 19:08:36 +0100 Subject: [PATCH 08/61] feat: add zkSync enum variant to HyperlaneDomainTechnicalStack (#4740) ### Description To match the new TS schema ### Drive-by changes ### Related issues ### Backward compatibility ### Testing --- rust/main/hyperlane-core/src/chain.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rust/main/hyperlane-core/src/chain.rs b/rust/main/hyperlane-core/src/chain.rs index a4cfaac77c..134881bf71 100644 --- a/rust/main/hyperlane-core/src/chain.rs +++ b/rust/main/hyperlane-core/src/chain.rs @@ -299,6 +299,7 @@ pub enum HyperlaneDomainTechnicalStack { OpStack, PolygonCDK, PolkadotSubstrate, + ZkSync, #[default] Other, } @@ -382,6 +383,7 @@ impl KnownHyperlaneDomain { HyperlaneDomainTechnicalStack::PolkadotSubstrate: [ Moonbeam, Tangle ], + HyperlaneDomainTechnicalStack::ZkSync: [], HyperlaneDomainTechnicalStack::Other: [ Avalanche, BinanceSmartChain, Celo, EclipseMainnet, Endurance, Ethereum, FuseMainnet, Gnosis, Injective, Linea, Lukso, Neutron, Osmosis, Polygon, From b585de0c53f02a9ec73aeb401bf2f8ae9170f6c9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 23 Oct 2024 18:17:43 +0000 Subject: [PATCH 09/61] Version Packages (#4663) This PR was opened by the [Changesets release](https://github.com/changesets/action) GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated. # Releases ## @hyperlane-xyz/core@5.5.0 ### Minor Changes - 72c23c0d6: Added PRECISION and rateUpdateNonce to ensure compatibility of HypERC4626 ### Patch Changes - c9085afd9: Patched OPL2ToL1Ism to check for correct messageId for external call in verify - ec6b874b1: Added nonce to HypERC4626 - Updated dependencies [f1712deb7] - Updated dependencies [29341950e] - @hyperlane-xyz/utils@5.6.0 ## @hyperlane-xyz/cli@5.6.0 ### Minor Changes - 41035aac8: Add strategyUrl detect and validation in the beginning of `warp apply` Remove yaml transactions print from `warp apply` - 29341950e: Adds new `core check` command to compare local configuration and on chain deployments. Adds memoization to the EvmHookReader to avoid repeating configuration derivation - 32d0a67c2: Adds the warp check command to compare warp routes config files with on chain warp route deployments - 3662297fc: Add prompt in `warp init` command to choose if a trusted relayer should be used instead of making the choice by default for the user and enable the `--yes` flag to default to a trusted ISM - b1ff48bd1: Add rebasing yield route support into CLI/SDK - d41aa6928: Add `EthJsonRpcBlockParameterTag` enum for validating reorgPeriod - c3e9268f1: Add support for an arbitrary string in `reorgPeriod`, which is used as a block tag to get the finalized block. - a4d5d692f: Update `warp apply` such that it updates in place AND extends in a single call - 01e7070eb: updates the multi chain selection prompt by adding search functionality and an optional confirmation prompt for the current selection ### Patch Changes - e89f9e35d: Update registry to v4.7.0 - Updated dependencies [f1712deb7] - Updated dependencies [46044a2e9] - Updated dependencies [02a5b92ba] - Updated dependencies [29341950e] - Updated dependencies [8001bbbd6] - Updated dependencies [32d0a67c2] - Updated dependencies [b1ff48bd1] - Updated dependencies [d41aa6928] - Updated dependencies [c3e9268f1] - Updated dependencies [7d7bcc1a3] - Updated dependencies [7f3e0669d] - Updated dependencies [2317eca3c] - @hyperlane-xyz/utils@5.6.0 - @hyperlane-xyz/sdk@5.6.0 ## @hyperlane-xyz/sdk@5.6.0 ### Minor Changes - 46044a2e9: Deploy to odysseytestnet - 02a5b92ba: Enroll new validators. Add tx overrides when deploying ICA accounts. Core checker now surfaces owner violations for defaultHook and requiredHook. App checker temporarily ignores bytecode mismatch violations. - 29341950e: Adds new `core check` command to compare local configuration and on chain deployments. Adds memoization to the EvmHookReader to avoid repeating configuration derivation - 8001bbbd6: Add override to some transactions to fix warp apply - 32d0a67c2: Adds the warp check command to compare warp routes config files with on chain warp route deployments - b1ff48bd1: Add rebasing yield route support into CLI/SDK - d41aa6928: Add `EthJsonRpcBlockParameterTag` enum for validating reorgPeriod - c3e9268f1: Add support for an arbitrary string in `reorgPeriod`, which is used as a block tag to get the finalized block. - 7d7bcc1a3: Add deployments for mainnets: flow, metall2, polynomial ### Patch Changes - 7f3e0669d: Fix filtering non-evm addresses in appFromAddressesMapHelper - 2317eca3c: Set transaction overrides and add 10% gas limit buffer when sending message through HyperlaneCore. - Updated dependencies [f1712deb7] - Updated dependencies [29341950e] - Updated dependencies [c9085afd9] - Updated dependencies [ec6b874b1] - Updated dependencies [72c23c0d6] - @hyperlane-xyz/utils@5.6.0 - @hyperlane-xyz/core@5.5.0 ## @hyperlane-xyz/utils@5.6.0 ### Minor Changes - 29341950e: Adds new `core check` command to compare local configuration and on chain deployments. Adds memoization to the EvmHookReader to avoid repeating configuration derivation ### Patch Changes - f1712deb7: Fix objMerge implementation ## @hyperlane-xyz/helloworld@5.6.0 ### Patch Changes - e89f9e35d: Update registry to v4.7.0 - Updated dependencies [46044a2e9] - Updated dependencies [02a5b92ba] - Updated dependencies [29341950e] - Updated dependencies [8001bbbd6] - Updated dependencies [32d0a67c2] - Updated dependencies [c9085afd9] - Updated dependencies [b1ff48bd1] - Updated dependencies [d41aa6928] - Updated dependencies [ec6b874b1] - Updated dependencies [c3e9268f1] - Updated dependencies [72c23c0d6] - Updated dependencies [7d7bcc1a3] - Updated dependencies [7f3e0669d] - Updated dependencies [2317eca3c] - @hyperlane-xyz/sdk@5.6.0 - @hyperlane-xyz/core@5.5.0 ## @hyperlane-xyz/widgets@5.6.0 ### Patch Changes - e89f9e35d: Update registry to v4.7.0 - Updated dependencies [f1712deb7] - Updated dependencies [46044a2e9] - Updated dependencies [02a5b92ba] - Updated dependencies [29341950e] - Updated dependencies [8001bbbd6] - Updated dependencies [32d0a67c2] - Updated dependencies [b1ff48bd1] - Updated dependencies [d41aa6928] - Updated dependencies [c3e9268f1] - Updated dependencies [7d7bcc1a3] - Updated dependencies [7f3e0669d] - Updated dependencies [2317eca3c] - @hyperlane-xyz/utils@5.6.0 - @hyperlane-xyz/sdk@5.6.0 ## @hyperlane-xyz/infra@5.6.0 ### Minor Changes - b3495b205: Updates the warpIds for Renzo's latest deployment to Sei and Taiko to be used by the Checker - c3e9268f1: Add support for an arbitrary string in `reorgPeriod`, which is used as a block tag to get the finalized block. ### Patch Changes - Updated dependencies [f1712deb7] - Updated dependencies [46044a2e9] - Updated dependencies [02a5b92ba] - Updated dependencies [29341950e] - Updated dependencies [8001bbbd6] - Updated dependencies [32d0a67c2] - Updated dependencies [b1ff48bd1] - Updated dependencies [e89f9e35d] - Updated dependencies [d41aa6928] - Updated dependencies [c3e9268f1] - Updated dependencies [7d7bcc1a3] - Updated dependencies [7f3e0669d] - Updated dependencies [2317eca3c] - @hyperlane-xyz/utils@5.6.0 - @hyperlane-xyz/sdk@5.6.0 - @hyperlane-xyz/helloworld@5.6.0 ## @hyperlane-xyz/ccip-server@5.6.0 ## @hyperlane-xyz/github-proxy@5.6.0 --------- Co-authored-by: github-actions[bot] --- .changeset/dirty-clocks-repeat.md | 5 ---- .changeset/dirty-months-buy.md | 5 ---- .changeset/dry-foxes-battle.md | 6 ----- .changeset/few-goats-add.md | 5 ---- .changeset/flat-swans-help.md | 7 ------ .changeset/fluffy-ducks-buy.md | 5 ---- .changeset/grumpy-ears-relate.md | 6 ----- .changeset/itchy-singers-hang.md | 5 ---- .changeset/long-swans-drive.md | 5 ---- .changeset/plenty-chicken-clean.md | 6 ----- .changeset/poor-coins-accept.md | 7 ------ .changeset/quiet-cooks-join.md | 6 ----- .changeset/red-actors-shop.md | 5 ---- .changeset/silver-dancers-rhyme.md | 5 ---- .changeset/strange-beers-buy.md | 7 ------ .changeset/sweet-humans-argue.md | 5 ---- .changeset/tender-spiders-deny.md | 5 ---- .changeset/tidy-hornets-beam.md | 5 ---- .changeset/tough-bats-yell.md | 5 ---- .changeset/tricky-mangos-sin.md | 5 ---- .changeset/wise-camels-repair.md | 5 ---- solidity/CHANGELOG.md | 14 +++++++++++ solidity/contracts/PackageVersioned.sol | 2 +- solidity/package.json | 4 +-- typescript/ccip-server/CHANGELOG.md | 2 ++ typescript/ccip-server/package.json | 2 +- typescript/cli/CHANGELOG.md | 33 +++++++++++++++++++++++++ typescript/cli/package.json | 6 ++--- typescript/cli/src/version.ts | 2 +- typescript/github-proxy/CHANGELOG.md | 2 ++ typescript/github-proxy/package.json | 2 +- typescript/helloworld/CHANGELOG.md | 22 +++++++++++++++++ typescript/helloworld/package.json | 6 ++--- typescript/infra/CHANGELOG.md | 26 +++++++++++++++++++ typescript/infra/package.json | 8 +++--- typescript/sdk/CHANGELOG.md | 26 +++++++++++++++++++ typescript/sdk/package.json | 6 ++--- typescript/utils/CHANGELOG.md | 10 ++++++++ typescript/utils/package.json | 2 +- typescript/widgets/CHANGELOG.md | 20 +++++++++++++++ typescript/widgets/package.json | 6 ++--- yarn.lock | 32 ++++++++++++------------ 42 files changed, 194 insertions(+), 154 deletions(-) delete mode 100644 .changeset/dirty-clocks-repeat.md delete mode 100644 .changeset/dirty-months-buy.md delete mode 100644 .changeset/dry-foxes-battle.md delete mode 100644 .changeset/few-goats-add.md delete mode 100644 .changeset/flat-swans-help.md delete mode 100644 .changeset/fluffy-ducks-buy.md delete mode 100644 .changeset/grumpy-ears-relate.md delete mode 100644 .changeset/itchy-singers-hang.md delete mode 100644 .changeset/long-swans-drive.md delete mode 100644 .changeset/plenty-chicken-clean.md delete mode 100644 .changeset/poor-coins-accept.md delete mode 100644 .changeset/quiet-cooks-join.md delete mode 100644 .changeset/red-actors-shop.md delete mode 100644 .changeset/silver-dancers-rhyme.md delete mode 100644 .changeset/strange-beers-buy.md delete mode 100644 .changeset/sweet-humans-argue.md delete mode 100644 .changeset/tender-spiders-deny.md delete mode 100644 .changeset/tidy-hornets-beam.md delete mode 100644 .changeset/tough-bats-yell.md delete mode 100644 .changeset/tricky-mangos-sin.md delete mode 100644 .changeset/wise-camels-repair.md diff --git a/.changeset/dirty-clocks-repeat.md b/.changeset/dirty-clocks-repeat.md deleted file mode 100644 index d9159825a6..0000000000 --- a/.changeset/dirty-clocks-repeat.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperlane-xyz/utils': patch ---- - -Fix objMerge implementation diff --git a/.changeset/dirty-months-buy.md b/.changeset/dirty-months-buy.md deleted file mode 100644 index 2b3f91e997..0000000000 --- a/.changeset/dirty-months-buy.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperlane-xyz/sdk': minor ---- - -Deploy to odysseytestnet diff --git a/.changeset/dry-foxes-battle.md b/.changeset/dry-foxes-battle.md deleted file mode 100644 index eadb8427e7..0000000000 --- a/.changeset/dry-foxes-battle.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@hyperlane-xyz/cli': minor ---- - -Add strategyUrl detect and validation in the beginning of `warp apply` -Remove yaml transactions print from `warp apply` diff --git a/.changeset/few-goats-add.md b/.changeset/few-goats-add.md deleted file mode 100644 index ffcab1f496..0000000000 --- a/.changeset/few-goats-add.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperlane-xyz/sdk': minor ---- - -Enroll new validators. Add tx overrides when deploying ICA accounts. Core checker now surfaces owner violations for defaultHook and requiredHook. App checker temporarily ignores bytecode mismatch violations. diff --git a/.changeset/flat-swans-help.md b/.changeset/flat-swans-help.md deleted file mode 100644 index e5f0e21118..0000000000 --- a/.changeset/flat-swans-help.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@hyperlane-xyz/utils': minor -'@hyperlane-xyz/cli': minor -'@hyperlane-xyz/sdk': minor ---- - -Adds new `core check` command to compare local configuration and on chain deployments. Adds memoization to the EvmHookReader to avoid repeating configuration derivation diff --git a/.changeset/fluffy-ducks-buy.md b/.changeset/fluffy-ducks-buy.md deleted file mode 100644 index 1b5db50977..0000000000 --- a/.changeset/fluffy-ducks-buy.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperlane-xyz/sdk': minor ---- - -Add override to some transactions to fix warp apply diff --git a/.changeset/grumpy-ears-relate.md b/.changeset/grumpy-ears-relate.md deleted file mode 100644 index 8dbc0a5151..0000000000 --- a/.changeset/grumpy-ears-relate.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@hyperlane-xyz/cli': minor -'@hyperlane-xyz/sdk': minor ---- - -Adds the warp check command to compare warp routes config files with on chain warp route deployments diff --git a/.changeset/itchy-singers-hang.md b/.changeset/itchy-singers-hang.md deleted file mode 100644 index 97096ff1a9..0000000000 --- a/.changeset/itchy-singers-hang.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperlane-xyz/core': patch ---- - -Patched OPL2ToL1Ism to check for correct messageId for external call in verify diff --git a/.changeset/long-swans-drive.md b/.changeset/long-swans-drive.md deleted file mode 100644 index 3f21e56c41..0000000000 --- a/.changeset/long-swans-drive.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperlane-xyz/cli': minor ---- - -Add prompt in `warp init` command to choose if a trusted relayer should be used instead of making the choice by default for the user and enable the `--yes` flag to default to a trusted ISM diff --git a/.changeset/plenty-chicken-clean.md b/.changeset/plenty-chicken-clean.md deleted file mode 100644 index e35a59eff6..0000000000 --- a/.changeset/plenty-chicken-clean.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@hyperlane-xyz/cli': minor -'@hyperlane-xyz/sdk': minor ---- - -Add rebasing yield route support into CLI/SDK diff --git a/.changeset/poor-coins-accept.md b/.changeset/poor-coins-accept.md deleted file mode 100644 index 2c3c6a892e..0000000000 --- a/.changeset/poor-coins-accept.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@hyperlane-xyz/helloworld': patch -'@hyperlane-xyz/widgets': patch -'@hyperlane-xyz/cli': patch ---- - -Update registry to v4.7.0 diff --git a/.changeset/quiet-cooks-join.md b/.changeset/quiet-cooks-join.md deleted file mode 100644 index a5f39f2ab4..0000000000 --- a/.changeset/quiet-cooks-join.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@hyperlane-xyz/cli': minor -'@hyperlane-xyz/sdk': minor ---- - -Add `EthJsonRpcBlockParameterTag` enum for validating reorgPeriod diff --git a/.changeset/red-actors-shop.md b/.changeset/red-actors-shop.md deleted file mode 100644 index 0ee301e903..0000000000 --- a/.changeset/red-actors-shop.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperlane-xyz/core': patch ---- - -Added nonce to HypERC4626 diff --git a/.changeset/silver-dancers-rhyme.md b/.changeset/silver-dancers-rhyme.md deleted file mode 100644 index 14ce7dd00d..0000000000 --- a/.changeset/silver-dancers-rhyme.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperlane-xyz/infra': minor ---- - -Updates the warpIds for Renzo's latest deployment to Sei and Taiko to be used by the Checker diff --git a/.changeset/strange-beers-buy.md b/.changeset/strange-beers-buy.md deleted file mode 100644 index 3ad5bc57ec..0000000000 --- a/.changeset/strange-beers-buy.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@hyperlane-xyz/infra': minor -'@hyperlane-xyz/cli': minor -'@hyperlane-xyz/sdk': minor ---- - -Add support for an arbitrary string in `reorgPeriod`, which is used as a block tag to get the finalized block. diff --git a/.changeset/sweet-humans-argue.md b/.changeset/sweet-humans-argue.md deleted file mode 100644 index 3a6ff46478..0000000000 --- a/.changeset/sweet-humans-argue.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperlane-xyz/core': minor ---- - -Added PRECISION and rateUpdateNonce to ensure compatibility of HypERC4626 diff --git a/.changeset/tender-spiders-deny.md b/.changeset/tender-spiders-deny.md deleted file mode 100644 index 99a6cede43..0000000000 --- a/.changeset/tender-spiders-deny.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperlane-xyz/cli': minor ---- - -Update `warp apply` such that it updates in place AND extends in a single call diff --git a/.changeset/tidy-hornets-beam.md b/.changeset/tidy-hornets-beam.md deleted file mode 100644 index b4cff7c2f3..0000000000 --- a/.changeset/tidy-hornets-beam.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperlane-xyz/sdk': minor ---- - -Add deployments for mainnets: flow, metall2, polynomial diff --git a/.changeset/tough-bats-yell.md b/.changeset/tough-bats-yell.md deleted file mode 100644 index 71343609bc..0000000000 --- a/.changeset/tough-bats-yell.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperlane-xyz/sdk': patch ---- - -Fix filtering non-evm addresses in appFromAddressesMapHelper diff --git a/.changeset/tricky-mangos-sin.md b/.changeset/tricky-mangos-sin.md deleted file mode 100644 index 31ea11b575..0000000000 --- a/.changeset/tricky-mangos-sin.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperlane-xyz/cli': minor ---- - -updates the multi chain selection prompt by adding search functionality and an optional confirmation prompt for the current selection diff --git a/.changeset/wise-camels-repair.md b/.changeset/wise-camels-repair.md deleted file mode 100644 index 7cde52ed60..0000000000 --- a/.changeset/wise-camels-repair.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperlane-xyz/sdk': patch ---- - -Set transaction overrides and add 10% gas limit buffer when sending message through HyperlaneCore. diff --git a/solidity/CHANGELOG.md b/solidity/CHANGELOG.md index 01d7e99ae8..dbffd611fb 100644 --- a/solidity/CHANGELOG.md +++ b/solidity/CHANGELOG.md @@ -1,5 +1,19 @@ # @hyperlane-xyz/core +## 5.5.0 + +### Minor Changes + +- 72c23c0d6: Added PRECISION and rateUpdateNonce to ensure compatibility of HypERC4626 + +### Patch Changes + +- c9085afd9: Patched OPL2ToL1Ism to check for correct messageId for external call in verify +- ec6b874b1: Added nonce to HypERC4626 +- Updated dependencies [f1712deb7] +- Updated dependencies [29341950e] + - @hyperlane-xyz/utils@5.6.0 + ## 5.4.1 ### Patch Changes diff --git a/solidity/contracts/PackageVersioned.sol b/solidity/contracts/PackageVersioned.sol index f0d43b725a..eb57706c12 100644 --- a/solidity/contracts/PackageVersioned.sol +++ b/solidity/contracts/PackageVersioned.sol @@ -7,5 +7,5 @@ pragma solidity >=0.6.11; **/ abstract contract PackageVersioned { // GENERATED CODE - DO NOT EDIT - string public constant PACKAGE_VERSION = "5.4.1"; + string public constant PACKAGE_VERSION = "5.5.0"; } diff --git a/solidity/package.json b/solidity/package.json index 7fb78e8783..78c42ca78d 100644 --- a/solidity/package.json +++ b/solidity/package.json @@ -1,11 +1,11 @@ { "name": "@hyperlane-xyz/core", "description": "Core solidity contracts for Hyperlane", - "version": "5.4.1", + "version": "5.5.0", "dependencies": { "@arbitrum/nitro-contracts": "^1.2.1", "@eth-optimism/contracts": "^0.6.0", - "@hyperlane-xyz/utils": "5.5.0", + "@hyperlane-xyz/utils": "5.6.0", "@layerzerolabs/lz-evm-oapp-v2": "2.0.2", "@openzeppelin/contracts": "^4.9.3", "@openzeppelin/contracts-upgradeable": "^v4.9.3", diff --git a/typescript/ccip-server/CHANGELOG.md b/typescript/ccip-server/CHANGELOG.md index 2f082f6b26..6ec43d13cf 100644 --- a/typescript/ccip-server/CHANGELOG.md +++ b/typescript/ccip-server/CHANGELOG.md @@ -1,5 +1,7 @@ # @hyperlane-xyz/ccip-server +## 5.6.0 + ## 5.5.0 ## 5.4.0 diff --git a/typescript/ccip-server/package.json b/typescript/ccip-server/package.json index c58df422c2..457654431c 100644 --- a/typescript/ccip-server/package.json +++ b/typescript/ccip-server/package.json @@ -1,6 +1,6 @@ { "name": "@hyperlane-xyz/ccip-server", - "version": "5.5.0", + "version": "5.6.0", "description": "CCIP server", "typings": "dist/index.d.ts", "typedocMain": "src/index.ts", diff --git a/typescript/cli/CHANGELOG.md b/typescript/cli/CHANGELOG.md index 517b7ebe1e..1cd6506a13 100644 --- a/typescript/cli/CHANGELOG.md +++ b/typescript/cli/CHANGELOG.md @@ -1,5 +1,38 @@ # @hyperlane-xyz/cli +## 5.6.0 + +### Minor Changes + +- 41035aac8: Add strategyUrl detect and validation in the beginning of `warp apply` + Remove yaml transactions print from `warp apply` +- 29341950e: Adds new `core check` command to compare local configuration and on chain deployments. Adds memoization to the EvmHookReader to avoid repeating configuration derivation +- 32d0a67c2: Adds the warp check command to compare warp routes config files with on chain warp route deployments +- 3662297fc: Add prompt in `warp init` command to choose if a trusted relayer should be used instead of making the choice by default for the user and enable the `--yes` flag to default to a trusted ISM +- b1ff48bd1: Add rebasing yield route support into CLI/SDK +- d41aa6928: Add `EthJsonRpcBlockParameterTag` enum for validating reorgPeriod +- c3e9268f1: Add support for an arbitrary string in `reorgPeriod`, which is used as a block tag to get the finalized block. +- a4d5d692f: Update `warp apply` such that it updates in place AND extends in a single call +- 01e7070eb: updates the multi chain selection prompt by adding search functionality and an optional confirmation prompt for the current selection + +### Patch Changes + +- e89f9e35d: Update registry to v4.7.0 +- Updated dependencies [f1712deb7] +- Updated dependencies [46044a2e9] +- Updated dependencies [02a5b92ba] +- Updated dependencies [29341950e] +- Updated dependencies [8001bbbd6] +- Updated dependencies [32d0a67c2] +- Updated dependencies [b1ff48bd1] +- Updated dependencies [d41aa6928] +- Updated dependencies [c3e9268f1] +- Updated dependencies [7d7bcc1a3] +- Updated dependencies [7f3e0669d] +- Updated dependencies [2317eca3c] + - @hyperlane-xyz/utils@5.6.0 + - @hyperlane-xyz/sdk@5.6.0 + ## 5.5.0 ### Patch Changes diff --git a/typescript/cli/package.json b/typescript/cli/package.json index a014c8bbc3..ab516e294d 100644 --- a/typescript/cli/package.json +++ b/typescript/cli/package.json @@ -1,13 +1,13 @@ { "name": "@hyperlane-xyz/cli", - "version": "5.5.0", + "version": "5.6.0", "description": "A command-line utility for common Hyperlane operations", "dependencies": { "@aws-sdk/client-kms": "^3.577.0", "@aws-sdk/client-s3": "^3.577.0", "@hyperlane-xyz/registry": "4.7.0", - "@hyperlane-xyz/sdk": "5.5.0", - "@hyperlane-xyz/utils": "5.5.0", + "@hyperlane-xyz/sdk": "5.6.0", + "@hyperlane-xyz/utils": "5.6.0", "@inquirer/prompts": "^3.0.0", "ansi-escapes": "^7.0.0", "asn1.js": "^5.4.1", diff --git a/typescript/cli/src/version.ts b/typescript/cli/src/version.ts index ce5c04eb3a..d4a8ed991b 100644 --- a/typescript/cli/src/version.ts +++ b/typescript/cli/src/version.ts @@ -1 +1 @@ -export const VERSION = '5.5.0'; +export const VERSION = '5.6.0'; diff --git a/typescript/github-proxy/CHANGELOG.md b/typescript/github-proxy/CHANGELOG.md index a22fdc8abc..a71264e351 100644 --- a/typescript/github-proxy/CHANGELOG.md +++ b/typescript/github-proxy/CHANGELOG.md @@ -1,5 +1,7 @@ # @hyperlane-xyz/github-proxy +## 5.6.0 + ## 5.5.0 ## 5.4.0 diff --git a/typescript/github-proxy/package.json b/typescript/github-proxy/package.json index cefc3c0dcd..d8173c30da 100644 --- a/typescript/github-proxy/package.json +++ b/typescript/github-proxy/package.json @@ -1,7 +1,7 @@ { "name": "@hyperlane-xyz/github-proxy", "description": "Github proxy that adds the API key to requests", - "version": "5.5.0", + "version": "5.6.0", "private": true, "scripts": { "deploy": "wrangler deploy", diff --git a/typescript/helloworld/CHANGELOG.md b/typescript/helloworld/CHANGELOG.md index aac142144d..1d2f1535be 100644 --- a/typescript/helloworld/CHANGELOG.md +++ b/typescript/helloworld/CHANGELOG.md @@ -1,5 +1,27 @@ # @hyperlane-xyz/helloworld +## 5.6.0 + +### Patch Changes + +- e89f9e35d: Update registry to v4.7.0 +- Updated dependencies [46044a2e9] +- Updated dependencies [02a5b92ba] +- Updated dependencies [29341950e] +- Updated dependencies [8001bbbd6] +- Updated dependencies [32d0a67c2] +- Updated dependencies [c9085afd9] +- Updated dependencies [b1ff48bd1] +- Updated dependencies [d41aa6928] +- Updated dependencies [ec6b874b1] +- Updated dependencies [c3e9268f1] +- Updated dependencies [72c23c0d6] +- Updated dependencies [7d7bcc1a3] +- Updated dependencies [7f3e0669d] +- Updated dependencies [2317eca3c] + - @hyperlane-xyz/sdk@5.6.0 + - @hyperlane-xyz/core@5.5.0 + ## 5.5.0 ### Patch Changes diff --git a/typescript/helloworld/package.json b/typescript/helloworld/package.json index ca2d79f60a..927647d068 100644 --- a/typescript/helloworld/package.json +++ b/typescript/helloworld/package.json @@ -1,11 +1,11 @@ { "name": "@hyperlane-xyz/helloworld", "description": "A basic skeleton of an Hyperlane app", - "version": "5.5.0", + "version": "5.6.0", "dependencies": { - "@hyperlane-xyz/core": "5.4.1", + "@hyperlane-xyz/core": "5.5.0", "@hyperlane-xyz/registry": "4.7.0", - "@hyperlane-xyz/sdk": "5.5.0", + "@hyperlane-xyz/sdk": "5.6.0", "@openzeppelin/contracts-upgradeable": "^4.9.3", "ethers": "^5.7.2" }, diff --git a/typescript/infra/CHANGELOG.md b/typescript/infra/CHANGELOG.md index bd6476484c..3b623135c5 100644 --- a/typescript/infra/CHANGELOG.md +++ b/typescript/infra/CHANGELOG.md @@ -1,5 +1,31 @@ # @hyperlane-xyz/infra +## 5.6.0 + +### Minor Changes + +- b3495b205: Updates the warpIds for Renzo's latest deployment to Sei and Taiko to be used by the Checker +- c3e9268f1: Add support for an arbitrary string in `reorgPeriod`, which is used as a block tag to get the finalized block. + +### Patch Changes + +- Updated dependencies [f1712deb7] +- Updated dependencies [46044a2e9] +- Updated dependencies [02a5b92ba] +- Updated dependencies [29341950e] +- Updated dependencies [8001bbbd6] +- Updated dependencies [32d0a67c2] +- Updated dependencies [b1ff48bd1] +- Updated dependencies [e89f9e35d] +- Updated dependencies [d41aa6928] +- Updated dependencies [c3e9268f1] +- Updated dependencies [7d7bcc1a3] +- Updated dependencies [7f3e0669d] +- Updated dependencies [2317eca3c] + - @hyperlane-xyz/utils@5.6.0 + - @hyperlane-xyz/sdk@5.6.0 + - @hyperlane-xyz/helloworld@5.6.0 + ## 5.5.0 ### Patch Changes diff --git a/typescript/infra/package.json b/typescript/infra/package.json index d49058100c..9d1d090c55 100644 --- a/typescript/infra/package.json +++ b/typescript/infra/package.json @@ -1,7 +1,7 @@ { "name": "@hyperlane-xyz/infra", "description": "Infrastructure utilities for the Hyperlane Network", - "version": "5.5.0", + "version": "5.6.0", "dependencies": { "@arbitrum/sdk": "^3.0.0", "@aws-sdk/client-iam": "^3.74.0", @@ -13,10 +13,10 @@ "@ethersproject/hardware-wallets": "^5.7.0", "@ethersproject/providers": "^5.7.2", "@google-cloud/secret-manager": "^5.5.0", - "@hyperlane-xyz/helloworld": "5.5.0", + "@hyperlane-xyz/helloworld": "5.6.0", "@hyperlane-xyz/registry": "4.7.0", - "@hyperlane-xyz/sdk": "5.5.0", - "@hyperlane-xyz/utils": "5.5.0", + "@hyperlane-xyz/sdk": "5.6.0", + "@hyperlane-xyz/utils": "5.6.0", "@inquirer/prompts": "^5.3.8", "@nomiclabs/hardhat-etherscan": "^3.0.3", "@safe-global/api-kit": "1.3.0", diff --git a/typescript/sdk/CHANGELOG.md b/typescript/sdk/CHANGELOG.md index 058f6b8173..0cec012ff1 100644 --- a/typescript/sdk/CHANGELOG.md +++ b/typescript/sdk/CHANGELOG.md @@ -1,5 +1,31 @@ # @hyperlane-xyz/sdk +## 5.6.0 + +### Minor Changes + +- 46044a2e9: Deploy to odysseytestnet +- 02a5b92ba: Enroll new validators. Add tx overrides when deploying ICA accounts. Core checker now surfaces owner violations for defaultHook and requiredHook. App checker temporarily ignores bytecode mismatch violations. +- 29341950e: Adds new `core check` command to compare local configuration and on chain deployments. Adds memoization to the EvmHookReader to avoid repeating configuration derivation +- 8001bbbd6: Add override to some transactions to fix warp apply +- 32d0a67c2: Adds the warp check command to compare warp routes config files with on chain warp route deployments +- b1ff48bd1: Add rebasing yield route support into CLI/SDK +- d41aa6928: Add `EthJsonRpcBlockParameterTag` enum for validating reorgPeriod +- c3e9268f1: Add support for an arbitrary string in `reorgPeriod`, which is used as a block tag to get the finalized block. +- 7d7bcc1a3: Add deployments for mainnets: flow, metall2, polynomial + +### Patch Changes + +- 7f3e0669d: Fix filtering non-evm addresses in appFromAddressesMapHelper +- 2317eca3c: Set transaction overrides and add 10% gas limit buffer when sending message through HyperlaneCore. +- Updated dependencies [f1712deb7] +- Updated dependencies [29341950e] +- Updated dependencies [c9085afd9] +- Updated dependencies [ec6b874b1] +- Updated dependencies [72c23c0d6] + - @hyperlane-xyz/utils@5.6.0 + - @hyperlane-xyz/core@5.5.0 + ## 5.5.0 ### Minor Changes diff --git a/typescript/sdk/package.json b/typescript/sdk/package.json index 6290277c8e..1a5e86aa8f 100644 --- a/typescript/sdk/package.json +++ b/typescript/sdk/package.json @@ -1,14 +1,14 @@ { "name": "@hyperlane-xyz/sdk", "description": "The official SDK for the Hyperlane Network", - "version": "5.5.0", + "version": "5.6.0", "dependencies": { "@arbitrum/sdk": "^4.0.0", "@aws-sdk/client-s3": "^3.74.0", "@cosmjs/cosmwasm-stargate": "^0.32.4", "@cosmjs/stargate": "^0.32.4", - "@hyperlane-xyz/core": "5.4.1", - "@hyperlane-xyz/utils": "5.5.0", + "@hyperlane-xyz/core": "5.5.0", + "@hyperlane-xyz/utils": "5.6.0", "@safe-global/api-kit": "1.3.0", "@safe-global/protocol-kit": "1.3.0", "@safe-global/safe-deployments": "1.37.8", diff --git a/typescript/utils/CHANGELOG.md b/typescript/utils/CHANGELOG.md index a9252e5633..e22dca07ab 100644 --- a/typescript/utils/CHANGELOG.md +++ b/typescript/utils/CHANGELOG.md @@ -1,5 +1,15 @@ # @hyperlane-xyz/utils +## 5.6.0 + +### Minor Changes + +- 29341950e: Adds new `core check` command to compare local configuration and on chain deployments. Adds memoization to the EvmHookReader to avoid repeating configuration derivation + +### Patch Changes + +- f1712deb7: Fix objMerge implementation + ## 5.5.0 ### Minor Changes diff --git a/typescript/utils/package.json b/typescript/utils/package.json index 83c8ed9c3d..9baa234bf0 100644 --- a/typescript/utils/package.json +++ b/typescript/utils/package.json @@ -1,7 +1,7 @@ { "name": "@hyperlane-xyz/utils", "description": "General utilities and types for the Hyperlane network", - "version": "5.5.0", + "version": "5.6.0", "dependencies": { "@cosmjs/encoding": "^0.32.4", "@solana/web3.js": "^1.78.0", diff --git a/typescript/widgets/CHANGELOG.md b/typescript/widgets/CHANGELOG.md index 8e58694274..e4698dd658 100644 --- a/typescript/widgets/CHANGELOG.md +++ b/typescript/widgets/CHANGELOG.md @@ -1,5 +1,25 @@ # @hyperlane-xyz/widgets +## 5.6.0 + +### Patch Changes + +- e89f9e35d: Update registry to v4.7.0 +- Updated dependencies [f1712deb7] +- Updated dependencies [46044a2e9] +- Updated dependencies [02a5b92ba] +- Updated dependencies [29341950e] +- Updated dependencies [8001bbbd6] +- Updated dependencies [32d0a67c2] +- Updated dependencies [b1ff48bd1] +- Updated dependencies [d41aa6928] +- Updated dependencies [c3e9268f1] +- Updated dependencies [7d7bcc1a3] +- Updated dependencies [7f3e0669d] +- Updated dependencies [2317eca3c] + - @hyperlane-xyz/utils@5.6.0 + - @hyperlane-xyz/sdk@5.6.0 + ## 5.5.0 ### Minor Changes diff --git a/typescript/widgets/package.json b/typescript/widgets/package.json index 2e01772460..031a874797 100644 --- a/typescript/widgets/package.json +++ b/typescript/widgets/package.json @@ -1,15 +1,15 @@ { "name": "@hyperlane-xyz/widgets", "description": "Common react components for Hyperlane projects", - "version": "5.5.0", + "version": "5.6.0", "peerDependencies": { "react": "^18", "react-dom": "^18" }, "dependencies": { "@headlessui/react": "^2.1.8", - "@hyperlane-xyz/sdk": "5.5.0", - "@hyperlane-xyz/utils": "5.5.0", + "@hyperlane-xyz/sdk": "5.6.0", + "@hyperlane-xyz/utils": "5.6.0", "clsx": "^2.1.1", "react-tooltip": "^5.28.0" }, diff --git a/yarn.lock b/yarn.lock index ebfca2e5c8..556803a51d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7824,8 +7824,8 @@ __metadata: "@ethersproject/abi": "npm:*" "@ethersproject/providers": "npm:*" "@hyperlane-xyz/registry": "npm:4.7.0" - "@hyperlane-xyz/sdk": "npm:5.5.0" - "@hyperlane-xyz/utils": "npm:5.5.0" + "@hyperlane-xyz/sdk": "npm:5.6.0" + "@hyperlane-xyz/utils": "npm:5.6.0" "@inquirer/prompts": "npm:^3.0.0" "@types/chai-as-promised": "npm:^8" "@types/mocha": "npm:^10.0.1" @@ -7858,13 +7858,13 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/core@npm:5.4.1, @hyperlane-xyz/core@workspace:solidity": +"@hyperlane-xyz/core@npm:5.5.0, @hyperlane-xyz/core@workspace:solidity": version: 0.0.0-use.local resolution: "@hyperlane-xyz/core@workspace:solidity" dependencies: "@arbitrum/nitro-contracts": "npm:^1.2.1" "@eth-optimism/contracts": "npm:^0.6.0" - "@hyperlane-xyz/utils": "npm:5.5.0" + "@hyperlane-xyz/utils": "npm:5.6.0" "@layerzerolabs/lz-evm-oapp-v2": "npm:2.0.2" "@layerzerolabs/solidity-examples": "npm:^1.1.0" "@nomiclabs/hardhat-ethers": "npm:^2.2.3" @@ -7915,13 +7915,13 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/helloworld@npm:5.5.0, @hyperlane-xyz/helloworld@workspace:typescript/helloworld": +"@hyperlane-xyz/helloworld@npm:5.6.0, @hyperlane-xyz/helloworld@workspace:typescript/helloworld": version: 0.0.0-use.local resolution: "@hyperlane-xyz/helloworld@workspace:typescript/helloworld" dependencies: - "@hyperlane-xyz/core": "npm:5.4.1" + "@hyperlane-xyz/core": "npm:5.5.0" "@hyperlane-xyz/registry": "npm:4.7.0" - "@hyperlane-xyz/sdk": "npm:5.5.0" + "@hyperlane-xyz/sdk": "npm:5.6.0" "@nomiclabs/hardhat-ethers": "npm:^2.2.3" "@nomiclabs/hardhat-waffle": "npm:^2.0.6" "@openzeppelin/contracts-upgradeable": "npm:^4.9.3" @@ -7968,10 +7968,10 @@ __metadata: "@ethersproject/hardware-wallets": "npm:^5.7.0" "@ethersproject/providers": "npm:^5.7.2" "@google-cloud/secret-manager": "npm:^5.5.0" - "@hyperlane-xyz/helloworld": "npm:5.5.0" + "@hyperlane-xyz/helloworld": "npm:5.6.0" "@hyperlane-xyz/registry": "npm:4.7.0" - "@hyperlane-xyz/sdk": "npm:5.5.0" - "@hyperlane-xyz/utils": "npm:5.5.0" + "@hyperlane-xyz/sdk": "npm:5.6.0" + "@hyperlane-xyz/utils": "npm:5.6.0" "@inquirer/prompts": "npm:^5.3.8" "@nomiclabs/hardhat-ethers": "npm:^2.2.3" "@nomiclabs/hardhat-etherscan": "npm:^3.0.3" @@ -8037,7 +8037,7 @@ __metadata: languageName: node linkType: hard -"@hyperlane-xyz/sdk@npm:5.5.0, @hyperlane-xyz/sdk@workspace:typescript/sdk": +"@hyperlane-xyz/sdk@npm:5.6.0, @hyperlane-xyz/sdk@workspace:typescript/sdk": version: 0.0.0-use.local resolution: "@hyperlane-xyz/sdk@workspace:typescript/sdk" dependencies: @@ -8045,8 +8045,8 @@ __metadata: "@aws-sdk/client-s3": "npm:^3.74.0" "@cosmjs/cosmwasm-stargate": "npm:^0.32.4" "@cosmjs/stargate": "npm:^0.32.4" - "@hyperlane-xyz/core": "npm:5.4.1" - "@hyperlane-xyz/utils": "npm:5.5.0" + "@hyperlane-xyz/core": "npm:5.5.0" + "@hyperlane-xyz/utils": "npm:5.6.0" "@nomiclabs/hardhat-ethers": "npm:^2.2.3" "@nomiclabs/hardhat-waffle": "npm:^2.0.6" "@safe-global/api-kit": "npm:1.3.0" @@ -8087,7 +8087,7 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/utils@npm:5.5.0, @hyperlane-xyz/utils@workspace:typescript/utils": +"@hyperlane-xyz/utils@npm:5.6.0, @hyperlane-xyz/utils@workspace:typescript/utils": version: 0.0.0-use.local resolution: "@hyperlane-xyz/utils@workspace:typescript/utils" dependencies: @@ -8113,8 +8113,8 @@ __metadata: dependencies: "@headlessui/react": "npm:^2.1.8" "@hyperlane-xyz/registry": "npm:4.7.0" - "@hyperlane-xyz/sdk": "npm:5.5.0" - "@hyperlane-xyz/utils": "npm:5.5.0" + "@hyperlane-xyz/sdk": "npm:5.6.0" + "@hyperlane-xyz/utils": "npm:5.6.0" "@storybook/addon-essentials": "npm:^7.6.14" "@storybook/addon-interactions": "npm:^7.6.14" "@storybook/addon-links": "npm:^7.6.14" From 8cc0d9a4ae5dac32864eafff09b292ba31853b67 Mon Sep 17 00:00:00 2001 From: Kunal Arora <55632507+aroralanuk@users.noreply.github.com> Date: Thu, 24 Oct 2024 00:31:56 +0530 Subject: [PATCH 10/61] feat(contracts): add wrapped HypERC4626 for ease of defi use (#4563) ### Description ### Drive-by changes None ### Related issues - closes https://github.com/chainlight-io/2024-08-hyperlane/issues/7 ### Backward compatibility Yes ### Testing Unit --- .changeset/itchy-bananas-know.md | 5 + .changeset/shiny-baboons-hunt.md | 5 + .../token/extensions/WHypERC4626.sol | 113 +++++++++++++++++ solidity/test/token/WHypERC4626.t.sol | 116 ++++++++++++++++++ 4 files changed, 239 insertions(+) create mode 100644 .changeset/itchy-bananas-know.md create mode 100644 .changeset/shiny-baboons-hunt.md create mode 100644 solidity/contracts/token/extensions/WHypERC4626.sol create mode 100644 solidity/test/token/WHypERC4626.t.sol diff --git a/.changeset/itchy-bananas-know.md b/.changeset/itchy-bananas-know.md new file mode 100644 index 0000000000..1889817569 --- /dev/null +++ b/.changeset/itchy-bananas-know.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/core': patch +--- + +Add wrapped HypERC4626 for easy defi use diff --git a/.changeset/shiny-baboons-hunt.md b/.changeset/shiny-baboons-hunt.md new file mode 100644 index 0000000000..ef302d93fa --- /dev/null +++ b/.changeset/shiny-baboons-hunt.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/core': minor +--- + +Added WHypERC4626 as a wrapper for rebasing HypERC4626 diff --git a/solidity/contracts/token/extensions/WHypERC4626.sol b/solidity/contracts/token/extensions/WHypERC4626.sol new file mode 100644 index 0000000000..08cb597321 --- /dev/null +++ b/solidity/contracts/token/extensions/WHypERC4626.sol @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.8.0; + +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import {HypERC4626} from "./HypERC4626.sol"; +import {PackageVersioned} from "../../PackageVersioned.sol"; + +/*@@@@@@@ @@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@ HYPERLANE @@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ +@@@@@@@@@ @@@@@@@@*/ + +/** + * @title WHypERC4626 + * @author Abacus Works + * @notice A wrapper for HypERC4626 that allows for wrapping and unwrapping of underlying rebasing tokens + */ +contract WHypERC4626 is ERC20, PackageVersioned { + HypERC4626 public immutable underlying; + + constructor( + HypERC4626 _underlying, + string memory name, + string memory symbol + ) ERC20(name, symbol) { + underlying = _underlying; + } + + /* + * @notice Wraps an amount of underlying tokens into wrapped tokens + * @param _underlyingAmount The amount of underlying tokens to wrap + * @return The amount of wrapped tokens + */ + function wrap(uint256 _underlyingAmount) external returns (uint256) { + require( + _underlyingAmount > 0, + "WHypERC4626: wrap amount must be greater than 0" + ); + uint256 wrappedAmount = underlying.assetsToShares(_underlyingAmount); + _mint(msg.sender, wrappedAmount); + underlying.transferFrom(msg.sender, address(this), _underlyingAmount); + return wrappedAmount; + } + + /* + * @notice Unwraps an amount of wrapped tokens into underlying tokens + * @param _wrappedAmount The amount of wrapped tokens to unwrap + * @return The amount of underlying tokens + */ + function unwrap(uint256 _wrappedAmount) external returns (uint256) { + require( + _wrappedAmount > 0, + "WHypERC4626: unwrap amount must be greater than 0" + ); + uint256 underlyingAmount = underlying.sharesToAssets(_wrappedAmount); + _burn(msg.sender, _wrappedAmount); + underlying.transfer(msg.sender, underlyingAmount); + return underlyingAmount; + } + + /* + * @notice Gets the amount of wrapped tokens for a given amount of underlying tokens + * @param _underlyingAmount The amount of underlying tokens + * @return The amount of wrapped tokens + */ + function getWrappedAmount( + uint256 _underlyingAmount + ) external view returns (uint256) { + return underlying.assetsToShares(_underlyingAmount); + } + + /* + * @notice Gets the amount of underlying tokens for a given amount of wrapped tokens + * @param _wrappedAmount The amount of wrapped tokens + * @return The amount of underlying tokens + */ + function getUnderlyingAmount( + uint256 _wrappedAmount + ) external view returns (uint256) { + return underlying.sharesToAssets(_wrappedAmount); + } + + /* + * @notice Gets the amount of wrapped tokens for 1 unit of underlying tokens + * @return The amount of wrapped tokens + */ + function wrappedPerUnderlying() external view returns (uint256) { + return underlying.assetsToShares(1 * 10 ** underlying.decimals()); + } + + /* + * @notice Gets the amount of underlying tokens for 1 unit of wrapped tokens + * @return The amount of underlying tokens + */ + function underlyingPerWrapped() external view returns (uint256) { + return underlying.sharesToAssets(1 * 10 ** decimals()); + } + + /* + * @notice Gets the decimals of the wrapped token + * @return The decimals of the wrapped token + */ + function decimals() public view override returns (uint8) { + return underlying.decimals(); + } +} diff --git a/solidity/test/token/WHypERC4626.t.sol b/solidity/test/token/WHypERC4626.t.sol new file mode 100644 index 0000000000..5684eeee28 --- /dev/null +++ b/solidity/test/token/WHypERC4626.t.sol @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.13; + +import {Test} from "forge-std/Test.sol"; +import {MockMailbox} from "../../contracts/mock/MockMailbox.sol"; +import {WHypERC4626} from "../../contracts/token/extensions/WHypERC4626.sol"; +import {HypERC4626} from "../../contracts/token/extensions/HypERC4626.sol"; +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract MockHypERC4626 is HypERC4626 { + constructor(address _mailbox) HypERC4626(18, _mailbox, 2) {} + + function mint(address to, uint256 amount) external { + _mint(to, amount); + } +} + +contract WHypERC4626Test is Test { + WHypERC4626 public wHypERC4626; + MockHypERC4626 public underlyingToken; + address public alice = address(0x1); + address public bob = address(0x2); + + function setUp() public { + MockMailbox mailbox = new MockMailbox(1); + underlyingToken = new MockHypERC4626(address(mailbox)); + wHypERC4626 = new WHypERC4626( + underlyingToken, + "Wrapped Rebasing Token", + "WRT" + ); + + underlyingToken.mint(alice, 1000 * 10 ** 18); + underlyingToken.mint(bob, 1000 * 10 ** 18); + } + + function test_wrap() public { + uint256 amount = 100 * 10 ** 18; + + vm.startPrank(alice); + underlyingToken.approve(address(wHypERC4626), amount); + uint256 wrappedAmount = wHypERC4626.wrap(amount); + + assertEq(wHypERC4626.balanceOf(alice), wrappedAmount); + assertEq(underlyingToken.balanceOf(alice), 900 * 10 ** 18); + vm.stopPrank(); + } + + function test_wrap_revertsWhen_zeroAmount() public { + vm.startPrank(alice); + underlyingToken.approve(address(wHypERC4626), 0); + vm.expectRevert("WHypERC4626: wrap amount must be greater than 0"); + wHypERC4626.wrap(0); + vm.stopPrank(); + } + + function test_unwrap() public { + uint256 amount = 100 * 10 ** 18; + + vm.startPrank(alice); + underlyingToken.approve(address(wHypERC4626), amount); + uint256 wrappedAmount = wHypERC4626.wrap(amount); + + uint256 unwrappedAmount = wHypERC4626.unwrap(wrappedAmount); + + assertEq(wHypERC4626.balanceOf(alice), 0); + assertEq(underlyingToken.balanceOf(alice), 1000 * 10 ** 18); + assertEq(unwrappedAmount, amount); + vm.stopPrank(); + } + + function test_unwrap_revertsWhen_zeroAmount() public { + vm.startPrank(alice); + vm.expectRevert("WHypERC4626: unwrap amount must be greater than 0"); + wHypERC4626.unwrap(0); + vm.stopPrank(); + } + + function test_getWrappedAmount() public view { + uint256 amount = 100 * 10 ** 18; + uint256 wrappedAmount = wHypERC4626.getWrappedAmount(amount); + + assertEq(wrappedAmount, underlyingToken.assetsToShares(amount)); + } + + function test_getUnderlyingAmount() public view { + uint256 amount = 100 * 10 ** 18; + uint256 underlyingAmount = wHypERC4626.getUnderlyingAmount(amount); + + assertEq(underlyingAmount, underlyingToken.sharesToAssets(amount)); + } + + function test_wrappedPerUnderlying() public view { + uint256 wrappedPerUnderlying = wHypERC4626.wrappedPerUnderlying(); + + assertEq( + wrappedPerUnderlying, + underlyingToken.assetsToShares(1 * 10 ** underlyingToken.decimals()) + ); + } + + function test_underlyingPerWrapped() public view { + uint256 underlyingPerWrapped = wHypERC4626.underlyingPerWrapped(); + + assertEq( + underlyingPerWrapped, + underlyingToken.sharesToAssets(1 * 10 ** underlyingToken.decimals()) + ); + } + + function test_decimals() public view { + uint8 decimals = wHypERC4626.decimals(); + + assertEq(decimals, underlyingToken.decimals()); + } +} From c55257cf5f897e0656e62d567ab6474321bc1906 Mon Sep 17 00:00:00 2001 From: Kunal Arora <55632507+aroralanuk@users.noreply.github.com> Date: Thu, 24 Oct 2024 00:56:53 +0530 Subject: [PATCH 11/61] fix(contracts): minor token related changes (#4580) ### Description Minor token related changes like adding custom hook to 4626 collateral, checking for ERC20 as valid contract in HypERC20Collateral, etc. ### Drive-by changes check for overflow in bytes32ToAddress ### Related issues - partly fixes https://github.com/chainlight-io/2024-08-hyperlane/issues/14 ### Backward compatibility Yes ### Testing Unit tests --- .changeset/perfect-dryers-destroy.md | 5 ++ solidity/contracts/client/GasRouter.sol | 16 +++++ solidity/contracts/libs/TypeCasts.sol | 4 ++ .../contracts/token/HypERC20Collateral.sol | 16 +++++ .../token/extensions/HypERC4626Collateral.sol | 16 ++--- solidity/test/GasRouter.t.sol | 2 + solidity/test/isms/RateLimitedIsm.t.sol | 2 +- solidity/test/token/HypERC20.t.sol | 5 ++ solidity/test/token/HypERC4626Test.t.sol | 68 +++++++++++++++---- 9 files changed, 110 insertions(+), 24 deletions(-) create mode 100644 .changeset/perfect-dryers-destroy.md diff --git a/.changeset/perfect-dryers-destroy.md b/.changeset/perfect-dryers-destroy.md new file mode 100644 index 0000000000..f9a906ef13 --- /dev/null +++ b/.changeset/perfect-dryers-destroy.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/core': minor +--- + +Minor token related changes like adding custom hook to 4626 collateral, checking for ERC20 as valid contract in HypERC20Collateral, etc. diff --git a/solidity/contracts/client/GasRouter.sol b/solidity/contracts/client/GasRouter.sol index c5f9efd1c1..f14d0b642c 100644 --- a/solidity/contracts/client/GasRouter.sol +++ b/solidity/contracts/client/GasRouter.sol @@ -1,10 +1,25 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity >=0.6.11; +/*@@@@@@@ @@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@ HYPERLANE @@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ +@@@@@@@@@ @@@@@@@@*/ + +// ============ Internal Imports ============ import {Router} from "./Router.sol"; import {StandardHookMetadata} from "../hooks/libs/StandardHookMetadata.sol"; abstract contract GasRouter is Router { + event GasSet(uint32 domain, uint256 gas); + // ============ Mutable Storage ============ mapping(uint32 => uint256) public destinationGas; @@ -56,6 +71,7 @@ abstract contract GasRouter is Router { function _setDestinationGas(uint32 domain, uint256 gas) internal { destinationGas[domain] = gas; + emit GasSet(domain, gas); } function _GasRouter_dispatch( diff --git a/solidity/contracts/libs/TypeCasts.sol b/solidity/contracts/libs/TypeCasts.sol index 4440f63da0..d156e98501 100644 --- a/solidity/contracts/libs/TypeCasts.sol +++ b/solidity/contracts/libs/TypeCasts.sol @@ -9,6 +9,10 @@ library TypeCasts { // alignment preserving cast function bytes32ToAddress(bytes32 _buf) internal pure returns (address) { + require( + uint256(_buf) <= uint256(type(uint160).max), + "TypeCasts: bytes32ToAddress overflow" + ); return address(uint160(uint256(_buf))); } } diff --git a/solidity/contracts/token/HypERC20Collateral.sol b/solidity/contracts/token/HypERC20Collateral.sol index 858a3140b9..69b137e75d 100644 --- a/solidity/contracts/token/HypERC20Collateral.sol +++ b/solidity/contracts/token/HypERC20Collateral.sol @@ -1,10 +1,25 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity >=0.8.0; +/*@@@@@@@ @@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@ HYPERLANE @@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ +@@@@@@@@@ @@@@@@@@*/ + +// ============ Internal Imports ============ import {TokenRouter} from "./libs/TokenRouter.sol"; import {TokenMessage} from "./libs/TokenMessage.sol"; import {MailboxClient} from "../client/MailboxClient.sol"; +// ============ External Imports ============ +import {Address} from "@openzeppelin/contracts/utils/Address.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; @@ -22,6 +37,7 @@ contract HypERC20Collateral is TokenRouter { * @param erc20 Address of the token to keep as collateral */ constructor(address erc20, address _mailbox) TokenRouter(_mailbox) { + require(Address.isContract(erc20), "HypERC20Collateral: invalid token"); wrappedToken = IERC20(erc20); } diff --git a/solidity/contracts/token/extensions/HypERC4626Collateral.sol b/solidity/contracts/token/extensions/HypERC4626Collateral.sol index 87528b1095..1f164563e7 100644 --- a/solidity/contracts/token/extensions/HypERC4626Collateral.sol +++ b/solidity/contracts/token/extensions/HypERC4626Collateral.sol @@ -66,11 +66,7 @@ contract HypERC4626Collateral is HypERC20Collateral { // Can't override _transferFromSender only because we need to pass shares in the token message _transferFromSender(_amount); uint256 _shares = _depositIntoVault(_amount); - uint256 _exchangeRate = PRECISION.mulDiv( - vault.totalAssets(), - vault.totalSupply(), - Math.Rounding.Down - ); + uint256 _exchangeRate = vault.convertToAssets(PRECISION); rateUpdateNonce++; bytes memory _tokenMetadata = abi.encode( @@ -121,15 +117,19 @@ contract HypERC4626Collateral is HypERC20Collateral { * @dev Update the exchange rate on the synthetic token by accounting for additional yield accrued to the underlying vault * @param _destinationDomain domain of the vault */ - function rebase(uint32 _destinationDomain) public payable { + function rebase( + uint32 _destinationDomain, + bytes calldata _hookMetadata, + address _hook + ) public payable { // force a rebase with an empty transfer to 0x1 _transferRemote( _destinationDomain, NULL_RECIPIENT, 0, msg.value, - bytes(""), - address(0) + _hookMetadata, + _hook ); } } diff --git a/solidity/test/GasRouter.t.sol b/solidity/test/GasRouter.t.sol index aaf73b2a3c..becd43cf38 100644 --- a/solidity/test/GasRouter.t.sol +++ b/solidity/test/GasRouter.t.sol @@ -68,6 +68,8 @@ contract GasRouterTest is Test { } function testSetDestinationGas(uint256 gas) public { + vm.expectEmit(true, true, true, true); + emit GasRouter.GasSet(originDomain, gas); setDestinationGas(remoteRouter, originDomain, gas); assertEq(remoteRouter.destinationGas(originDomain), gas); diff --git a/solidity/test/isms/RateLimitedIsm.t.sol b/solidity/test/isms/RateLimitedIsm.t.sol index 7c4b76356b..481bbaa498 100644 --- a/solidity/test/isms/RateLimitedIsm.t.sol +++ b/solidity/test/isms/RateLimitedIsm.t.sol @@ -75,7 +75,7 @@ contract RateLimitedIsmTest is Test { TokenMessage.format(bytes32(""), _amount, bytes("")) ); - vm.expectRevert("InvalidRecipient"); + vm.expectRevert("TypeCasts: bytes32ToAddress overflow"); rateLimitedIsm.verify(bytes(""), _message); } diff --git a/solidity/test/token/HypERC20.t.sol b/solidity/test/token/HypERC20.t.sol index 9c068b2393..c3b7da74cd 100644 --- a/solidity/test/token/HypERC20.t.sol +++ b/solidity/test/token/HypERC20.t.sol @@ -406,6 +406,11 @@ contract HypERC20CollateralTest is HypTokenTest { _enrollRemoteTokenRouter(); } + function test_constructor_revert_ifInvalidToken() public { + vm.expectRevert("HypERC20Collateral: invalid token"); + new HypERC20Collateral(address(0), address(localMailbox)); + } + function testInitialize_revert_ifAlreadyInitialized() public {} function testRemoteTransfer() public { diff --git a/solidity/test/token/HypERC4626Test.t.sol b/solidity/test/token/HypERC4626Test.t.sol index 338d39b75d..ef92431489 100644 --- a/solidity/test/token/HypERC4626Test.t.sol +++ b/solidity/test/token/HypERC4626Test.t.sol @@ -24,7 +24,9 @@ import {MockMailbox} from "../../contracts/mock/MockMailbox.sol"; import {HypERC20} from "../../contracts/token/HypERC20.sol"; import {HypERC4626Collateral} from "../../contracts/token/extensions/HypERC4626Collateral.sol"; import {HypERC4626} from "../../contracts/token/extensions/HypERC4626.sol"; +import {StandardHookMetadata} from "../../contracts/hooks/libs/StandardHookMetadata.sol"; import "../../contracts/test/ERC4626/ERC4626Test.sol"; +import {ProtocolFee} from "../../contracts/hooks/ProtocolFee.sol"; contract HypERC4626CollateralTest is HypTokenTest { using TypeCasts for address; @@ -124,14 +126,38 @@ contract HypERC4626CollateralTest is HypTokenTest { _accrueYield(); - localRebasingToken.rebase(DESTINATION); + localRebasingToken.rebase(DESTINATION, bytes(""), address(0)); remoteMailbox.processNextInboundMessage(); - assertEq( + assertApproxEqRelDecimal( remoteToken.balanceOf(BOB), - transferAmount + _discountedYield() + transferAmount + _discountedYield(), + 1e14, + 0 ); } + function testRemoteTransfer_rebaseWithCustomHook() public { + _performRemoteTransferWithoutExpectation(0, transferAmount); + assertEq(remoteToken.balanceOf(BOB), transferAmount); + + _accrueYield(); + + uint256 FEE = 1e18; + ProtocolFee customHook = new ProtocolFee( + FEE, + FEE, + address(this), + address(this) + ); + + localRebasingToken.rebase{value: FEE}( + DESTINATION, + StandardHookMetadata.overrideMsgValue(FEE), + address(customHook) + ); + assertEq(address(customHook).balance, FEE); + } + function testRebaseWithTransfer() public { _performRemoteTransferWithoutExpectation(0, transferAmount); assertEq(remoteToken.balanceOf(BOB), transferAmount); @@ -275,7 +301,7 @@ contract HypERC4626CollateralTest is HypTokenTest { _accrueYield(); - localRebasingToken.rebase(DESTINATION); + localRebasingToken.rebase(DESTINATION, bytes(""), address(0)); remoteMailbox.processNextInboundMessage(); // Use balance here since it might be off by <1bp @@ -314,7 +340,7 @@ contract HypERC4626CollateralTest is HypTokenTest { _accrueYield(); _accrueYield(); // earning 2x yield to be split - localRebasingToken.rebase(DESTINATION); + localRebasingToken.rebase(DESTINATION, bytes(""), address(0)); vm.prank(CAROL); remoteToken.transferRemote( @@ -352,7 +378,7 @@ contract HypERC4626CollateralTest is HypTokenTest { // decrease collateral in vault by 10% uint256 drawdown = 5e18; primaryToken.burnFrom(address(vault), drawdown); - localRebasingToken.rebase(DESTINATION); + localRebasingToken.rebase(DESTINATION, bytes(""), address(0)); remoteMailbox.processNextInboundMessage(); // Use balance here since it might be off by <1bp @@ -378,7 +404,7 @@ contract HypERC4626CollateralTest is HypTokenTest { _accrueYield(); - localRebasingToken.rebase(DESTINATION); + localRebasingToken.rebase(DESTINATION, bytes(""), address(0)); remoteMailbox.processNextInboundMessage(); vm.prank(BOB); @@ -389,13 +415,23 @@ contract HypERC4626CollateralTest is HypTokenTest { ); peerMailbox.processNextInboundMessage(); - assertEq(remoteRebasingToken.exchangeRate(), 1045e7); // 5 * 0.9 = 4.5% yield + assertApproxEqRelDecimal( + remoteRebasingToken.exchangeRate(), + 1045e7, + 1e14, + 0 + ); // 5 * 0.9 = 4.5% yield assertEq(peerRebasingToken.exchangeRate(), 1e10); // assertingthat transfers by the synthetic variant don't impact the exchang rate - localRebasingToken.rebase(PEER_DESTINATION); + localRebasingToken.rebase(PEER_DESTINATION, bytes(""), address(0)); peerMailbox.processNextInboundMessage(); - assertEq(peerRebasingToken.exchangeRate(), 1045e7); // asserting that the exchange rate is set finally by the collateral variant + assertApproxEqRelDecimal( + peerRebasingToken.exchangeRate(), + 1045e7, + 1e14, + 0 + ); // asserting that the exchange rate is set finally by the collateral variant } function test_cyclicTransfers() public { @@ -405,7 +441,7 @@ contract HypERC4626CollateralTest is HypTokenTest { _accrueYield(); - localRebasingToken.rebase(DESTINATION); // yield is added + localRebasingToken.rebase(DESTINATION, bytes(""), address(0)); // yield is added remoteMailbox.processNextInboundMessage(); // BOB: remote -> peer(BOB) (yield is leftover) @@ -417,7 +453,7 @@ contract HypERC4626CollateralTest is HypTokenTest { ); peerMailbox.processNextInboundMessage(); - localRebasingToken.rebase(PEER_DESTINATION); + localRebasingToken.rebase(PEER_DESTINATION, bytes(""), address(0)); peerMailbox.processNextInboundMessage(); // BOB: peer -> local(CAROL) @@ -457,11 +493,13 @@ contract HypERC4626CollateralTest is HypTokenTest { _accrueYield(); - localRebasingToken.rebase(DESTINATION); + localRebasingToken.rebase(DESTINATION, bytes(""), address(0)); remoteMailbox.processNextInboundMessage(); - assertEq( + assertApproxEqRelDecimal( remoteToken.balanceOf(BOB), - transferAmount + _discountedYield() + transferAmount + _discountedYield(), + 1e14, + 0 ); vm.prank(address(localMailbox)); From 29bf1d8c73ee82f1b6a3840a33a8236383192976 Mon Sep 17 00:00:00 2001 From: Paul Balaji <10051819+paulbalaji@users.noreply.github.com> Date: Wed, 23 Oct 2024 20:31:11 +0100 Subject: [PATCH 12/61] chore: set finalized reorgPeriod (#4741) ### Description - chore: set finalized reorgPeriod - update reorg period ### Drive-by changes na ### Related issues na ### Backward compatibility agents are already updated ### Testing manual Signed-off-by: pbio <10051819+paulbalaji@users.noreply.github.com> --- .registryrc | 2 +- rust/main/config/mainnet_config.json | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.registryrc b/.registryrc index 862c96776e..ab4e5304b3 100644 --- a/.registryrc +++ b/.registryrc @@ -1 +1 @@ -dee58183e51f4eb43e84dbac0e595a4b389dbe80 +5b4334afa6ff242c9eed872a5f666fcc7f880e27 diff --git a/rust/main/config/mainnet_config.json b/rust/main/config/mainnet_config.json index a562cd84e2..9d32c75781 100644 --- a/rust/main/config/mainnet_config.json +++ b/rust/main/config/mainnet_config.json @@ -429,7 +429,7 @@ "blocks": { "confirmations": 1, "estimateBlockTime": 3, - "reorgPeriod": 15 + "reorgPeriod": "finalized" }, "chainId": 56, "deployer": { @@ -1981,7 +1981,7 @@ "blocks": { "confirmations": 2, "estimateBlockTime": 12, - "reorgPeriod": 10 + "reorgPeriod": "finalized" }, "chainId": 1284, "deployer": { @@ -2258,7 +2258,7 @@ "blocks": { "confirmations": 3, "estimateBlockTime": 2, - "reorgPeriod": 256 + "reorgPeriod": "finalized" }, "chainId": 137, "deployer": { @@ -2912,7 +2912,7 @@ "blocks": { "confirmations": 1, "estimateBlockTime": 6, - "reorgPeriod": 10 + "reorgPeriod": "finalized" }, "chainId": 5845, "deployer": { @@ -3446,7 +3446,7 @@ "blocks": { "confirmations": 1, "estimateBlockTime": 13, - "reorgPeriod": 32 + "reorgPeriod": "finalized" }, "chainId": 592, "deployer": { @@ -3924,7 +3924,7 @@ "blocks": { "confirmations": 1, "estimateBlockTime": 5, - "reorgPeriod": 5 + "reorgPeriod": "finalized" }, "chainId": 109, "deployer": { @@ -4187,7 +4187,7 @@ "blocks": { "confirmations": 1, "estimateBlockTime": 3, - "reorgPeriod": 9 + "reorgPeriod": "finalized" }, "chainId": 88888, "deployer": { From a64af8be9a76120d0cfc727bb70660fa07e70cce Mon Sep 17 00:00:00 2001 From: Paul Balaji <10051819+paulbalaji@users.noreply.github.com> Date: Thu, 24 Oct 2024 12:57:23 +0100 Subject: [PATCH 13/61] fix: revert chiliz reorg period back to 9 (#4744) ### Description fix: revert chiliz reorg period back to 9 ### Drive-by changes na ### Related issues na ### Backward compatibility ye ### Testing relayer sad times - [context](https://discord.com/channels/935678348330434570/1298963431743029359/1298965217757102100) --- .registryrc | 2 +- rust/main/config/mainnet_config.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.registryrc b/.registryrc index ab4e5304b3..a694b325bd 100644 --- a/.registryrc +++ b/.registryrc @@ -1 +1 @@ -5b4334afa6ff242c9eed872a5f666fcc7f880e27 +65b3c54ede7bf2b56280b913b8ed57bb08f52a36 diff --git a/rust/main/config/mainnet_config.json b/rust/main/config/mainnet_config.json index 9d32c75781..7f147b6e06 100644 --- a/rust/main/config/mainnet_config.json +++ b/rust/main/config/mainnet_config.json @@ -4187,7 +4187,7 @@ "blocks": { "confirmations": 1, "estimateBlockTime": 3, - "reorgPeriod": "finalized" + "reorgPeriod": 9 }, "chainId": 88888, "deployer": { From ffbe1dd82e2452dbc111b6fb469a34fb870da8f1 Mon Sep 17 00:00:00 2001 From: Danil Nemirovsky Date: Thu, 24 Oct 2024 15:21:55 +0100 Subject: [PATCH 14/61] feat: Make HyperlaneProvider to request block by height, not by hash (#4727) ### Description Make HyperlaneProvider to request block by height, not by hash. Since it is hard to find a block by hash for Solana, we switch to use block height. We have to do it for all type of chains since we need to change signature of chain-agnostic method ### Drive-by changes * Small method rename ### Related issues - Contributes into https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/4272 ### Backward compatibility Yes ### Testing Manual run of Scraper for Ethereum and Neutron chains. --------- Co-authored-by: Danil Nemirovsky <4614623+ameten@users.noreply.github.com> --- .../agents/scraper/src/chain_scraper/mod.rs | 32 +++++---- .../src/providers/cosmos/provider.rs | 33 ++++------ .../src/rpc_clients/provider.rs | 65 +++++++++++++------ .../chains/hyperlane-fuel/src/provider.rs | 35 ++++++---- .../chains/hyperlane-sealevel/src/error.rs | 3 + .../chains/hyperlane-sealevel/src/mailbox.rs | 2 +- .../chains/hyperlane-sealevel/src/provider.rs | 27 ++++++-- .../hyperlane-sealevel/src/rpc/client.rs | 25 +++++-- rust/main/hyperlane-core/src/error.rs | 3 - .../hyperlane-core/src/traits/provider.rs | 22 ++++--- .../main/hyperlane-core/src/types/block_id.rs | 17 +++++ rust/main/hyperlane-core/src/types/mod.rs | 2 + 12 files changed, 181 insertions(+), 85 deletions(-) create mode 100644 rust/main/hyperlane-core/src/types/block_id.rs diff --git a/rust/main/agents/scraper/src/chain_scraper/mod.rs b/rust/main/agents/scraper/src/chain_scraper/mod.rs index 71ddd84efb..c23df70a16 100644 --- a/rust/main/agents/scraper/src/chain_scraper/mod.rs +++ b/rust/main/agents/scraper/src/chain_scraper/mod.rs @@ -9,7 +9,7 @@ use async_trait::async_trait; use eyre::Result; use hyperlane_base::settings::IndexSettings; use hyperlane_core::{ - unwrap_or_none_result, BlockInfo, Delivery, HyperlaneDomain, HyperlaneLogStore, + unwrap_or_none_result, BlockId, BlockInfo, Delivery, HyperlaneDomain, HyperlaneLogStore, HyperlaneMessage, HyperlaneProvider, HyperlaneSequenceAwareIndexerStoreReader, HyperlaneWatermarkedLogStore, Indexed, InterchainGasPayment, LogMeta, H256, }; @@ -78,13 +78,13 @@ impl HyperlaneSqlDb { &self, log_meta: impl Iterator, ) -> Result> { - let block_hash_by_txn_hash: HashMap = log_meta + let block_id_by_txn_hash: HashMap = log_meta .map(|meta| { ( meta.transaction_id .try_into() .expect("256-bit transaction ids are the maximum supported at this time"), - meta.block_hash, + BlockId::new(meta.block_hash, meta.block_number), ) }) .collect(); @@ -92,16 +92,16 @@ impl HyperlaneSqlDb { // all blocks we care about // hash of block maps to the block id and timestamp let blocks: HashMap<_, _> = self - .ensure_blocks(block_hash_by_txn_hash.values().copied()) + .ensure_blocks(block_id_by_txn_hash.values().copied()) .await? .map(|block| (block.hash, block)) .collect(); trace!(?blocks, "Ensured blocks"); // We ensure transactions only from blocks which are inserted into database - let txn_hash_with_block_ids = block_hash_by_txn_hash + let txn_hash_with_block_ids = block_id_by_txn_hash .into_iter() - .filter_map(move |(txn, block)| blocks.get(&block).map(|b| (txn, b.id))) + .filter_map(move |(txn, block)| blocks.get(&block.hash).map(|b| (txn, b.id))) .map(|(txn_hash, block_id)| TxnWithBlockId { txn_hash, block_id }); let txns_with_ids = self.ensure_txns(txn_hash_with_block_ids).await?; @@ -195,11 +195,17 @@ impl HyperlaneSqlDb { /// this method. async fn ensure_blocks( &self, - block_hashes: impl Iterator, + block_ids: impl Iterator, ) -> Result> { + // Mapping from block hash to block ids (hash and height) + let block_hash_to_block_id_map: HashMap = + block_ids.map(|b| (b.hash, b)).collect(); + // Mapping of block hash to `BasicBlock` which contains database block id and block hash. - let mut blocks: HashMap> = - block_hashes.map(|b| (b, None)).collect(); + let mut blocks: HashMap> = block_hash_to_block_id_map + .keys() + .map(|hash| (*hash, None)) + .collect(); let db_blocks: Vec = if !blocks.is_empty() { // check database to see which blocks we already know and fetch their IDs @@ -230,10 +236,14 @@ impl HyperlaneSqlDb { for chunk in as_chunks(blocks_to_fetch, CHUNK_SIZE) { debug_assert!(!chunk.is_empty()); for (hash, block_info) in chunk { - let info = match self.provider.get_block_by_hash(hash).await { + // We should have block_id in this map for every hashes + let block_id = block_hash_to_block_id_map[hash]; + let block_height = block_id.height; + + let info = match self.provider.get_block_by_height(block_height).await { Ok(info) => info, Err(e) => { - warn!(?hash, ?e, "error fetching and parsing block"); + warn!(block_hash = ?hash, ?block_height, ?e, "error fetching and parsing block"); continue; } }; diff --git a/rust/main/chains/hyperlane-cosmos/src/providers/cosmos/provider.rs b/rust/main/chains/hyperlane-cosmos/src/providers/cosmos/provider.rs index 491f197765..ef0c153458 100644 --- a/rust/main/chains/hyperlane-cosmos/src/providers/cosmos/provider.rs +++ b/rust/main/chains/hyperlane-cosmos/src/providers/cosmos/provider.rs @@ -18,8 +18,8 @@ use tracing::{error, warn}; use crypto::decompress_public_key; use hyperlane_core::{ AccountAddressType, BlockInfo, ChainCommunicationError, ChainInfo, ChainResult, - ContractLocator, HyperlaneChain, HyperlaneDomain, HyperlaneProvider, TxnInfo, TxnReceiptInfo, - H256, U256, + ContractLocator, HyperlaneChain, HyperlaneDomain, HyperlaneProvider, HyperlaneProviderError, + TxnInfo, TxnReceiptInfo, H256, U256, }; use crate::grpc::{WasmGrpcProvider, WasmProvider}; @@ -367,33 +367,26 @@ impl HyperlaneChain for CosmosProvider { #[async_trait] impl HyperlaneProvider for CosmosProvider { - async fn get_block_by_hash(&self, hash: &H256) -> ChainResult { - let tendermint_hash = Hash::from_bytes(Algorithm::Sha256, hash.as_bytes()) - .expect("block hash should be of correct size"); - - let response = self.rpc_client.get_block_by_hash(tendermint_hash).await?; + async fn get_block_by_height(&self, height: u64) -> ChainResult { + let response = self.rpc_client.get_block(height as u32).await?; - let received_hash = H256::from_slice(response.block_id.hash.as_bytes()); + let block = response.block; + let block_height = block.header.height.value(); - if &received_hash != hash { - return Err(ChainCommunicationError::from_other_str( - &format!("received incorrect block, expected hash: {hash:?}, received hash: {received_hash:?}") - )); + if block_height != height { + Err(HyperlaneProviderError::IncorrectBlockByHeight( + height, + block_height, + ))? } - let block = response.block.ok_or_else(|| { - ChainCommunicationError::from_other_str(&format!( - "empty block info for block: {:?}", - hash - )) - })?; - + let hash = H256::from_slice(response.block_id.hash.as_bytes()); let time: OffsetDateTime = block.header.time.into(); let block_info = BlockInfo { hash: hash.to_owned(), timestamp: time.unix_timestamp() as u64, - number: block.header.height.value(), + number: block_height, }; Ok(block_info) diff --git a/rust/main/chains/hyperlane-ethereum/src/rpc_clients/provider.rs b/rust/main/chains/hyperlane-ethereum/src/rpc_clients/provider.rs index 8434a4de39..c8ef3d78ce 100644 --- a/rust/main/chains/hyperlane-ethereum/src/rpc_clients/provider.rs +++ b/rust/main/chains/hyperlane-ethereum/src/rpc_clients/provider.rs @@ -49,26 +49,49 @@ where { #[instrument(err, skip(self))] #[allow(clippy::blocks_in_conditions)] // TODO: `rustc` 1.80.1 clippy issue - async fn get_block_by_hash(&self, hash: &H256) -> ChainResult { - let block = get_with_retry_on_none(hash, |h| { - let eth_h256: ethers_core_types::H256 = h.into(); - self.provider.get_block(eth_h256) - }) + async fn get_block_by_height(&self, height: u64) -> ChainResult { + let block = get_with_retry_on_none( + &height, + |h| self.provider.get_block(*h), + |h| HyperlaneProviderError::CouldNotFindBlockByHeight(*h), + ) .await?; - Ok(BlockInfo { - hash: *hash, + + let block_height = block + .number + .ok_or(HyperlaneProviderError::CouldNotFindBlockByHeight(height))? + .as_u64(); + + if block_height != height { + Err(HyperlaneProviderError::IncorrectBlockByHeight( + height, + block_height, + ))?; + } + + let block_hash = block + .hash + .ok_or(HyperlaneProviderError::BlockWithoutHash(height))?; + + let block_info = BlockInfo { + hash: block_hash.into(), timestamp: block.timestamp.as_u64(), - number: block - .number - .ok_or(HyperlaneProviderError::BlockIsNotPartOfChainYet(*hash))? - .as_u64(), - }) + number: block_height, + }; + + Ok(block_info) } #[instrument(err, skip(self))] #[allow(clippy::blocks_in_conditions)] // TODO: `rustc` 1.80.1 clippy issue async fn get_txn_by_hash(&self, hash: &H256) -> ChainResult { - let txn = get_with_retry_on_none(hash, |h| self.provider.get_transaction(*h)).await?; + let txn = get_with_retry_on_none( + hash, + |h| self.provider.get_transaction(*h), + |h| HyperlaneProviderError::CouldNotFindTransactionByHash(*h), + ) + .await?; + let receipt = self .provider .get_transaction_receipt(*hash) @@ -193,22 +216,24 @@ impl BuildableWithProvider for HyperlaneProviderBuilder { /// Call a get function that returns a Result> and retry if the inner /// option is None. This can happen because the provider has not discovered the /// object we are looking for yet. -async fn get_with_retry_on_none(hash: &H256, get: F) -> ChainResult +async fn get_with_retry_on_none( + id: &I, + get: F, + not_found_error: N, +) -> ChainResult where - F: Fn(&H256) -> O, + F: Fn(&I) -> O, O: Future, E>>, E: std::error::Error + Send + Sync + 'static, + N: Fn(&I) -> HyperlaneProviderError, { for _ in 0..3 { - if let Some(t) = get(hash) - .await - .map_err(ChainCommunicationError::from_other)? - { + if let Some(t) = get(id).await.map_err(ChainCommunicationError::from_other)? { return Ok(t); } else { sleep(Duration::from_secs(5)).await; continue; }; } - Err(HyperlaneProviderError::CouldNotFindObjectByHash(*hash).into()) + Err(not_found_error(id).into()) } diff --git a/rust/main/chains/hyperlane-fuel/src/provider.rs b/rust/main/chains/hyperlane-fuel/src/provider.rs index e3836f141a..a72f8a6c5f 100644 --- a/rust/main/chains/hyperlane-fuel/src/provider.rs +++ b/rust/main/chains/hyperlane-fuel/src/provider.rs @@ -1,7 +1,6 @@ use std::{collections::HashMap, ops::Deref}; use async_trait::async_trait; - use fuels::{ client::{FuelClient, PageDirection, PaginationRequest}, prelude::Provider, @@ -13,13 +12,14 @@ use fuels::{ transaction::{Transaction, TransactionType}, transaction_response::TransactionResponse, tx_status::TxStatus, - Address, Bytes32, ContractId, + Address, BlockHeight, Bytes32, ContractId, }, }; use futures::future::join_all; use hyperlane_core::{ BlockInfo, ChainCommunicationError, ChainInfo, ChainResult, HyperlaneChain, HyperlaneDomain, - HyperlaneMessage, HyperlaneProvider, Indexed, LogMeta, TxnInfo, H256, H512, U256, + HyperlaneMessage, HyperlaneProvider, HyperlaneProviderError, Indexed, LogMeta, TxnInfo, H256, + H512, U256, }; use crate::{make_client, make_provider, prelude::FuelIntoH256, ConnectionConf}; @@ -285,19 +285,30 @@ impl HyperlaneChain for FuelProvider { impl HyperlaneProvider for FuelProvider { /// Used by scraper #[allow(clippy::clone_on_copy)] // TODO: `rustc` 1.80.1 clippy issue - async fn get_block_by_hash(&self, hash: &H256) -> ChainResult { - let block_res = self.provider.block(&hash.0.into()).await.map_err(|e| { - ChainCommunicationError::CustomError(format!("Failed to get block: {}", e)) - })?; + async fn get_block_by_height(&self, height: u64) -> ChainResult { + let block_res = self + .provider + .block_by_height(BlockHeight::new(height as u32)) + .await + .map_err(|e| HyperlaneProviderError::CouldNotFindBlockByHeight(height))?; - match block_res { - Some(block) => Ok(BlockInfo { + let block_info = match block_res { + Some(block) => BlockInfo { hash: H256::from_slice(block.id.as_slice()), - number: block.header.height.into(), timestamp: block.header.time.map_or(0, |t| t.timestamp() as u64), - }), - None => Err(ChainCommunicationError::BlockNotFound(hash.clone())), + number: block.header.height.into(), + }, + None => Err(HyperlaneProviderError::CouldNotFindBlockByHeight(height))?, + }; + + if block_info.number != height { + Err(HyperlaneProviderError::IncorrectBlockByHeight( + height, + block_info.number, + ))?; } + + Ok(block_info) } /// Used by scraper diff --git a/rust/main/chains/hyperlane-sealevel/src/error.rs b/rust/main/chains/hyperlane-sealevel/src/error.rs index 55b81b4167..ff0142c393 100644 --- a/rust/main/chains/hyperlane-sealevel/src/error.rs +++ b/rust/main/chains/hyperlane-sealevel/src/error.rs @@ -14,6 +14,9 @@ pub enum HyperlaneSealevelError { /// ClientError error #[error("{0}")] ClientError(#[from] ClientError), + /// Decoding error + #[error("{0}")] + Decoding(#[from] solana_sdk::bs58::decode::Error), } impl From for ChainCommunicationError { diff --git a/rust/main/chains/hyperlane-sealevel/src/mailbox.rs b/rust/main/chains/hyperlane-sealevel/src/mailbox.rs index 5453360e0b..5e348d9995 100644 --- a/rust/main/chains/hyperlane-sealevel/src/mailbox.rs +++ b/rust/main/chains/hyperlane-sealevel/src/mailbox.rs @@ -430,7 +430,7 @@ impl Mailbox for SealevelMailbox { let account = self .rpc() - .get_possible_account_with_finalized_commitment(&processed_message_account_key) + .get_account_option_with_finalized_commitment(&processed_message_account_key) .await?; Ok(account.is_some()) diff --git a/rust/main/chains/hyperlane-sealevel/src/provider.rs b/rust/main/chains/hyperlane-sealevel/src/provider.rs index b292d95941..aaf32ac593 100644 --- a/rust/main/chains/hyperlane-sealevel/src/provider.rs +++ b/rust/main/chains/hyperlane-sealevel/src/provider.rs @@ -1,11 +1,11 @@ use std::{str::FromStr, sync::Arc}; use async_trait::async_trait; - use hyperlane_core::{ - BlockInfo, ChainInfo, ChainResult, HyperlaneChain, HyperlaneDomain, HyperlaneProvider, TxnInfo, - H256, U256, + BlockInfo, ChainInfo, ChainResult, HyperlaneChain, HyperlaneDomain, HyperlaneProvider, + HyperlaneProviderError, TxnInfo, H256, U256, }; +use solana_sdk::bs58; use solana_sdk::pubkey::Pubkey; use crate::{error::HyperlaneSealevelError, ConnectionConf, SealevelRpcClient}; @@ -47,8 +47,25 @@ impl HyperlaneChain for SealevelProvider { #[async_trait] impl HyperlaneProvider for SealevelProvider { - async fn get_block_by_hash(&self, _hash: &H256) -> ChainResult { - todo!() // FIXME + async fn get_block_by_height(&self, slot: u64) -> ChainResult { + let confirmed_block = self.rpc_client.get_block(slot).await?; + + let hash_binary = bs58::decode(confirmed_block.blockhash) + .into_vec() + .map_err(HyperlaneSealevelError::Decoding)?; + let block_hash = H256::from_slice(&hash_binary); + + let block_time = confirmed_block + .block_time + .ok_or(HyperlaneProviderError::CouldNotFindBlockByHeight(slot))?; + + let block_info = BlockInfo { + hash: block_hash, + timestamp: block_time as u64, + number: slot, + }; + + Ok(block_info) } async fn get_txn_by_hash(&self, _hash: &H256) -> ChainResult { diff --git a/rust/main/chains/hyperlane-sealevel/src/rpc/client.rs b/rust/main/chains/hyperlane-sealevel/src/rpc/client.rs index 77f21ee1f0..44ba2c8d59 100644 --- a/rust/main/chains/hyperlane-sealevel/src/rpc/client.rs +++ b/rust/main/chains/hyperlane-sealevel/src/rpc/client.rs @@ -3,8 +3,8 @@ use borsh::{BorshDeserialize, BorshSerialize}; use hyperlane_core::{ChainCommunicationError, ChainResult, U256}; use serializable_account_meta::{SerializableAccountMeta, SimulationReturnData}; use solana_client::{ - nonblocking::rpc_client::RpcClient, rpc_config::RpcProgramAccountsConfig, - rpc_response::Response, + nonblocking::rpc_client::RpcClient, rpc_config::RpcBlockConfig, + rpc_config::RpcProgramAccountsConfig, rpc_response::Response, }; use solana_sdk::{ account::Account, @@ -16,7 +16,9 @@ use solana_sdk::{ signature::{Keypair, Signature, Signer}, transaction::Transaction, }; -use solana_transaction_status::{TransactionStatus, UiReturnDataEncoding, UiTransactionReturnData}; +use solana_transaction_status::{ + TransactionStatus, UiConfirmedBlock, UiReturnDataEncoding, UiTransactionReturnData, +}; use crate::error::HyperlaneSealevelError; @@ -79,12 +81,12 @@ impl SealevelRpcClient { &self, pubkey: &Pubkey, ) -> ChainResult { - self.get_possible_account_with_finalized_commitment(pubkey) + self.get_account_option_with_finalized_commitment(pubkey) .await? .ok_or_else(|| ChainCommunicationError::from_other_str("Could not find account data")) } - pub async fn get_possible_account_with_finalized_commitment( + pub async fn get_account_option_with_finalized_commitment( &self, pubkey: &Pubkey, ) -> ChainResult> { @@ -97,6 +99,19 @@ impl SealevelRpcClient { Ok(account) } + pub async fn get_block(&self, height: u64) -> ChainResult { + let config = RpcBlockConfig { + commitment: Some(CommitmentConfig::finalized()), + max_supported_transaction_version: Some(0), + ..Default::default() + }; + self.0 + .get_block_with_config(height, config) + .await + .map_err(HyperlaneSealevelError::ClientError) + .map_err(Into::into) + } + pub async fn get_block_height(&self) -> ChainResult { let height = self .0 diff --git a/rust/main/hyperlane-core/src/error.rs b/rust/main/hyperlane-core/src/error.rs index fe1385c0ed..63c0f36558 100644 --- a/rust/main/hyperlane-core/src/error.rs +++ b/rust/main/hyperlane-core/src/error.rs @@ -93,9 +93,6 @@ pub enum ChainCommunicationError { /// Failed to parse strings or integers #[error("Data parsing error {0:?}")] StrOrIntParseError(#[from] StrOrIntParseError), - /// BlockNotFoundError - #[error("Block not found: {0:?}")] - BlockNotFound(H256), /// utf8 error #[error("{0}")] Utf8(#[from] FromUtf8Error), diff --git a/rust/main/hyperlane-core/src/traits/provider.rs b/rust/main/hyperlane-core/src/traits/provider.rs index 654e80218f..47070bc2ab 100644 --- a/rust/main/hyperlane-core/src/traits/provider.rs +++ b/rust/main/hyperlane-core/src/traits/provider.rs @@ -16,8 +16,8 @@ use crate::{BlockInfo, ChainInfo, ChainResult, HyperlaneChain, TxnInfo, H256, U2 #[async_trait] #[auto_impl(&, Box, Arc)] pub trait HyperlaneProvider: HyperlaneChain + Send + Sync + Debug { - /// Get block info for a given block hash - async fn get_block_by_hash(&self, hash: &H256) -> ChainResult; + /// Get block info for a given block height + async fn get_block_by_height(&self, height: u64) -> ChainResult; /// Get txn info for a given txn hash async fn get_txn_by_hash(&self, hash: &H256) -> ChainResult; @@ -35,13 +35,19 @@ pub trait HyperlaneProvider: HyperlaneChain + Send + Sync + Debug { /// Errors when querying for provider information. #[derive(Error, Debug)] pub enum HyperlaneProviderError { - /// The requested block hash is not yet known by the provider - #[error("Block is not part of chain yet {0:?}")] - BlockIsNotPartOfChainYet(H256), /// The provider did not return the gas which was used #[error("Provider did not return gas used")] NoGasUsed, - /// Could not find a transaction, block, or other object - #[error("Could not find object from provider with hash {0:?}")] - CouldNotFindObjectByHash(H256), + /// Could not find a transaction by hash + #[error("Could not find transaction from provider with hash {0:?}")] + CouldNotFindTransactionByHash(H256), + /// Could not find a block by height + #[error("Could not find block from provider with height {0:?}")] + CouldNotFindBlockByHeight(u64), + /// The requested block does not have its hash + #[error("Block with height {0:?} does not contain its hash")] + BlockWithoutHash(u64), + /// Incorrect block is received + #[error("Requested block with height {0:?}, received block with height {1:?}")] + IncorrectBlockByHeight(u64, u64), } diff --git a/rust/main/hyperlane-core/src/types/block_id.rs b/rust/main/hyperlane-core/src/types/block_id.rs new file mode 100644 index 0000000000..57f69045ed --- /dev/null +++ b/rust/main/hyperlane-core/src/types/block_id.rs @@ -0,0 +1,17 @@ +use crate::H256; + +/// Struct `BlockId` contains two types of identifiers for the same block: hash and height. +#[derive(Debug, Default, Copy, Clone)] +pub struct BlockId { + /// Block hash + pub hash: H256, + /// Block height + pub height: u64, +} + +impl BlockId { + /// Creates instance of `BlockId` struct + pub fn new(hash: H256, height: u64) -> Self { + Self { hash, height } + } +} diff --git a/rust/main/hyperlane-core/src/types/mod.rs b/rust/main/hyperlane-core/src/types/mod.rs index 07989e83ef..a8973a72b3 100644 --- a/rust/main/hyperlane-core/src/types/mod.rs +++ b/rust/main/hyperlane-core/src/types/mod.rs @@ -9,6 +9,7 @@ pub use self::primitive_types::*; pub use ::primitive_types as ethers_core_types; pub use account_address_type::AccountAddressType; pub use announcement::*; +pub use block_id::BlockId; pub use chain_data::*; pub use checkpoint::*; pub use indexing::*; @@ -23,6 +24,7 @@ use crate::{Decode, Encode, HyperlaneProtocolError}; /// This module contains enum for account address type mod account_address_type; mod announcement; +mod block_id; mod chain_data; mod checkpoint; mod indexing; From 3474a8450716d649aed6cd5d54f0ee0d68ff9ee5 Mon Sep 17 00:00:00 2001 From: Paul Balaji <10051819+paulbalaji@users.noreply.github.com> Date: Thu, 24 Oct 2024 16:07:27 +0100 Subject: [PATCH 15/61] fix: update cli dependencies (#4746) ### Description fix: update cli dependencies ### Drive-by changes na ### Related issues https://discord.com/channels/935678348330434570/961710804011458621/1299011251858440324 ### Backward compatibility y ### Testing `npm pack` `npm install -g hyperlane-cli-local.tgz` --------- Signed-off-by: pbio <10051819+paulbalaji@users.noreply.github.com> --- .changeset/early-chicken-develop.md | 5 ++++ typescript/cli/package.json | 2 ++ yarn.lock | 46 +++++++++++++++-------------- 3 files changed, 31 insertions(+), 22 deletions(-) create mode 100644 .changeset/early-chicken-develop.md diff --git a/.changeset/early-chicken-develop.md b/.changeset/early-chicken-develop.md new file mode 100644 index 0000000000..c43d9342eb --- /dev/null +++ b/.changeset/early-chicken-develop.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/cli': patch +--- + +Explicitly define inquirer/core and inquirier/figures dependencies diff --git a/typescript/cli/package.json b/typescript/cli/package.json index ab516e294d..e55f6e4a7f 100644 --- a/typescript/cli/package.json +++ b/typescript/cli/package.json @@ -8,6 +8,8 @@ "@hyperlane-xyz/registry": "4.7.0", "@hyperlane-xyz/sdk": "5.6.0", "@hyperlane-xyz/utils": "5.6.0", + "@inquirer/core": "9.0.10", + "@inquirer/figures": "1.0.5", "@inquirer/prompts": "^3.0.0", "ansi-escapes": "^7.0.0", "asn1.js": "^5.4.1", diff --git a/yarn.lock b/yarn.lock index 556803a51d..40836ce5a2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7826,6 +7826,8 @@ __metadata: "@hyperlane-xyz/registry": "npm:4.7.0" "@hyperlane-xyz/sdk": "npm:5.6.0" "@hyperlane-xyz/utils": "npm:5.6.0" + "@inquirer/core": "npm:9.0.10" + "@inquirer/figures": "npm:1.0.5" "@inquirer/prompts": "npm:^3.0.0" "@types/chai-as-promised": "npm:^8" "@types/mocha": "npm:^10.0.1" @@ -8197,6 +8199,27 @@ __metadata: languageName: node linkType: hard +"@inquirer/core@npm:9.0.10, @inquirer/core@npm:^9.0.10": + version: 9.0.10 + resolution: "@inquirer/core@npm:9.0.10" + dependencies: + "@inquirer/figures": "npm:^1.0.5" + "@inquirer/type": "npm:^1.5.2" + "@types/mute-stream": "npm:^0.0.4" + "@types/node": "npm:^22.1.0" + "@types/wrap-ansi": "npm:^3.0.0" + ansi-escapes: "npm:^4.3.2" + cli-spinners: "npm:^2.9.2" + cli-width: "npm:^4.1.0" + mute-stream: "npm:^1.0.0" + signal-exit: "npm:^4.1.0" + strip-ansi: "npm:^6.0.1" + wrap-ansi: "npm:^6.2.0" + yoctocolors-cjs: "npm:^2.1.2" + checksum: 1bcb1deb7393d78f2dac5b8774d10692ad50b70e3ebc24684d13259d0c6c863dd1bce8ab4d4a806a6e90d5a2517aa8f9981993b1a256c9be68d9ef5e748481c6 + languageName: node + linkType: hard + "@inquirer/core@npm:^3.0.0": version: 3.0.0 resolution: "@inquirer/core@npm:3.0.0" @@ -8219,27 +8242,6 @@ __metadata: languageName: node linkType: hard -"@inquirer/core@npm:^9.0.10": - version: 9.0.10 - resolution: "@inquirer/core@npm:9.0.10" - dependencies: - "@inquirer/figures": "npm:^1.0.5" - "@inquirer/type": "npm:^1.5.2" - "@types/mute-stream": "npm:^0.0.4" - "@types/node": "npm:^22.1.0" - "@types/wrap-ansi": "npm:^3.0.0" - ansi-escapes: "npm:^4.3.2" - cli-spinners: "npm:^2.9.2" - cli-width: "npm:^4.1.0" - mute-stream: "npm:^1.0.0" - signal-exit: "npm:^4.1.0" - strip-ansi: "npm:^6.0.1" - wrap-ansi: "npm:^6.2.0" - yoctocolors-cjs: "npm:^2.1.2" - checksum: 1bcb1deb7393d78f2dac5b8774d10692ad50b70e3ebc24684d13259d0c6c863dd1bce8ab4d4a806a6e90d5a2517aa8f9981993b1a256c9be68d9ef5e748481c6 - languageName: node - linkType: hard - "@inquirer/editor@npm:^1.2.4": version: 1.2.4 resolution: "@inquirer/editor@npm:1.2.4" @@ -8286,7 +8288,7 @@ __metadata: languageName: node linkType: hard -"@inquirer/figures@npm:^1.0.5": +"@inquirer/figures@npm:1.0.5, @inquirer/figures@npm:^1.0.5": version: 1.0.5 resolution: "@inquirer/figures@npm:1.0.5" checksum: 60a51b2cdef03c89be25071c23d8c4ae427c56d8ac1b00bf054ca7be446674adc4edd66c15465fe6a81ff0726b024bf37f8a2903a8387ef968d33058da3e7a15 From c53e3302b3c70c21cd0b620e8f2365d48c2ce289 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 24 Oct 2024 15:32:58 +0000 Subject: [PATCH 16/61] Version Packages (#4742) This PR was opened by the [Changesets release](https://github.com/changesets/action) GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated. # Releases ## @hyperlane-xyz/core@5.6.0 ### Minor Changes - c55257cf5: Minor token related changes like adding custom hook to 4626 collateral, checking for ERC20 as valid contract in HypERC20Collateral, etc. - 8cc0d9a4a: Added WHypERC4626 as a wrapper for rebasing HypERC4626 ### Patch Changes - 8cc0d9a4a: Add wrapped HypERC4626 for easy defi use - @hyperlane-xyz/utils@5.6.1 ## @hyperlane-xyz/cli@5.6.1 ### Patch Changes - 3474a8450: Explicitly define inquirer/core and inquirier/figures dependencies - @hyperlane-xyz/sdk@5.6.1 - @hyperlane-xyz/utils@5.6.1 ## @hyperlane-xyz/helloworld@5.6.1 ### Patch Changes - Updated dependencies [8cc0d9a4a] - Updated dependencies [c55257cf5] - Updated dependencies [8cc0d9a4a] - @hyperlane-xyz/core@5.6.0 - @hyperlane-xyz/sdk@5.6.1 ## @hyperlane-xyz/sdk@5.6.1 ### Patch Changes - Updated dependencies [8cc0d9a4a] - Updated dependencies [c55257cf5] - Updated dependencies [8cc0d9a4a] - @hyperlane-xyz/core@5.6.0 - @hyperlane-xyz/utils@5.6.1 ## @hyperlane-xyz/widgets@5.6.1 ### Patch Changes - @hyperlane-xyz/sdk@5.6.1 - @hyperlane-xyz/utils@5.6.1 ## @hyperlane-xyz/utils@5.6.1 ## @hyperlane-xyz/infra@5.6.1 ### Patch Changes - @hyperlane-xyz/helloworld@5.6.1 - @hyperlane-xyz/sdk@5.6.1 - @hyperlane-xyz/utils@5.6.1 ## @hyperlane-xyz/ccip-server@5.6.1 ## @hyperlane-xyz/github-proxy@5.6.1 --------- Co-authored-by: github-actions[bot] --- .changeset/early-chicken-develop.md | 5 ---- .changeset/itchy-bananas-know.md | 5 ---- .changeset/perfect-dryers-destroy.md | 5 ---- .changeset/shiny-baboons-hunt.md | 5 ---- solidity/CHANGELOG.md | 12 ++++++++++ solidity/contracts/PackageVersioned.sol | 2 +- solidity/package.json | 4 ++-- typescript/ccip-server/CHANGELOG.md | 2 ++ typescript/ccip-server/package.json | 2 +- typescript/cli/CHANGELOG.md | 8 +++++++ typescript/cli/package.json | 6 ++--- typescript/cli/src/version.ts | 2 +- typescript/github-proxy/CHANGELOG.md | 2 ++ typescript/github-proxy/package.json | 2 +- typescript/helloworld/CHANGELOG.md | 10 ++++++++ typescript/helloworld/package.json | 6 ++--- typescript/infra/CHANGELOG.md | 8 +++++++ typescript/infra/package.json | 8 +++---- typescript/sdk/CHANGELOG.md | 10 ++++++++ typescript/sdk/package.json | 6 ++--- typescript/utils/CHANGELOG.md | 2 ++ typescript/utils/package.json | 2 +- typescript/widgets/CHANGELOG.md | 7 ++++++ typescript/widgets/package.json | 6 ++--- yarn.lock | 32 ++++++++++++------------- 25 files changed, 100 insertions(+), 59 deletions(-) delete mode 100644 .changeset/early-chicken-develop.md delete mode 100644 .changeset/itchy-bananas-know.md delete mode 100644 .changeset/perfect-dryers-destroy.md delete mode 100644 .changeset/shiny-baboons-hunt.md diff --git a/.changeset/early-chicken-develop.md b/.changeset/early-chicken-develop.md deleted file mode 100644 index c43d9342eb..0000000000 --- a/.changeset/early-chicken-develop.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperlane-xyz/cli': patch ---- - -Explicitly define inquirer/core and inquirier/figures dependencies diff --git a/.changeset/itchy-bananas-know.md b/.changeset/itchy-bananas-know.md deleted file mode 100644 index 1889817569..0000000000 --- a/.changeset/itchy-bananas-know.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperlane-xyz/core': patch ---- - -Add wrapped HypERC4626 for easy defi use diff --git a/.changeset/perfect-dryers-destroy.md b/.changeset/perfect-dryers-destroy.md deleted file mode 100644 index f9a906ef13..0000000000 --- a/.changeset/perfect-dryers-destroy.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperlane-xyz/core': minor ---- - -Minor token related changes like adding custom hook to 4626 collateral, checking for ERC20 as valid contract in HypERC20Collateral, etc. diff --git a/.changeset/shiny-baboons-hunt.md b/.changeset/shiny-baboons-hunt.md deleted file mode 100644 index ef302d93fa..0000000000 --- a/.changeset/shiny-baboons-hunt.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperlane-xyz/core': minor ---- - -Added WHypERC4626 as a wrapper for rebasing HypERC4626 diff --git a/solidity/CHANGELOG.md b/solidity/CHANGELOG.md index dbffd611fb..fbecf3809c 100644 --- a/solidity/CHANGELOG.md +++ b/solidity/CHANGELOG.md @@ -1,5 +1,17 @@ # @hyperlane-xyz/core +## 5.6.0 + +### Minor Changes + +- c55257cf5: Minor token related changes like adding custom hook to 4626 collateral, checking for ERC20 as valid contract in HypERC20Collateral, etc. +- 8cc0d9a4a: Added WHypERC4626 as a wrapper for rebasing HypERC4626 + +### Patch Changes + +- 8cc0d9a4a: Add wrapped HypERC4626 for easy defi use + - @hyperlane-xyz/utils@5.6.1 + ## 5.5.0 ### Minor Changes diff --git a/solidity/contracts/PackageVersioned.sol b/solidity/contracts/PackageVersioned.sol index eb57706c12..b1fa39c8ab 100644 --- a/solidity/contracts/PackageVersioned.sol +++ b/solidity/contracts/PackageVersioned.sol @@ -7,5 +7,5 @@ pragma solidity >=0.6.11; **/ abstract contract PackageVersioned { // GENERATED CODE - DO NOT EDIT - string public constant PACKAGE_VERSION = "5.5.0"; + string public constant PACKAGE_VERSION = "5.6.0"; } diff --git a/solidity/package.json b/solidity/package.json index 78c42ca78d..f8cbe196c9 100644 --- a/solidity/package.json +++ b/solidity/package.json @@ -1,11 +1,11 @@ { "name": "@hyperlane-xyz/core", "description": "Core solidity contracts for Hyperlane", - "version": "5.5.0", + "version": "5.6.0", "dependencies": { "@arbitrum/nitro-contracts": "^1.2.1", "@eth-optimism/contracts": "^0.6.0", - "@hyperlane-xyz/utils": "5.6.0", + "@hyperlane-xyz/utils": "5.6.1", "@layerzerolabs/lz-evm-oapp-v2": "2.0.2", "@openzeppelin/contracts": "^4.9.3", "@openzeppelin/contracts-upgradeable": "^v4.9.3", diff --git a/typescript/ccip-server/CHANGELOG.md b/typescript/ccip-server/CHANGELOG.md index 6ec43d13cf..08febc36fb 100644 --- a/typescript/ccip-server/CHANGELOG.md +++ b/typescript/ccip-server/CHANGELOG.md @@ -1,5 +1,7 @@ # @hyperlane-xyz/ccip-server +## 5.6.1 + ## 5.6.0 ## 5.5.0 diff --git a/typescript/ccip-server/package.json b/typescript/ccip-server/package.json index 457654431c..5a7c9cdf16 100644 --- a/typescript/ccip-server/package.json +++ b/typescript/ccip-server/package.json @@ -1,6 +1,6 @@ { "name": "@hyperlane-xyz/ccip-server", - "version": "5.6.0", + "version": "5.6.1", "description": "CCIP server", "typings": "dist/index.d.ts", "typedocMain": "src/index.ts", diff --git a/typescript/cli/CHANGELOG.md b/typescript/cli/CHANGELOG.md index 1cd6506a13..37fc5fea7a 100644 --- a/typescript/cli/CHANGELOG.md +++ b/typescript/cli/CHANGELOG.md @@ -1,5 +1,13 @@ # @hyperlane-xyz/cli +## 5.6.1 + +### Patch Changes + +- 3474a8450: Explicitly define inquirer/core and inquirier/figures dependencies + - @hyperlane-xyz/sdk@5.6.1 + - @hyperlane-xyz/utils@5.6.1 + ## 5.6.0 ### Minor Changes diff --git a/typescript/cli/package.json b/typescript/cli/package.json index e55f6e4a7f..9348646b80 100644 --- a/typescript/cli/package.json +++ b/typescript/cli/package.json @@ -1,13 +1,13 @@ { "name": "@hyperlane-xyz/cli", - "version": "5.6.0", + "version": "5.6.1", "description": "A command-line utility for common Hyperlane operations", "dependencies": { "@aws-sdk/client-kms": "^3.577.0", "@aws-sdk/client-s3": "^3.577.0", "@hyperlane-xyz/registry": "4.7.0", - "@hyperlane-xyz/sdk": "5.6.0", - "@hyperlane-xyz/utils": "5.6.0", + "@hyperlane-xyz/sdk": "5.6.1", + "@hyperlane-xyz/utils": "5.6.1", "@inquirer/core": "9.0.10", "@inquirer/figures": "1.0.5", "@inquirer/prompts": "^3.0.0", diff --git a/typescript/cli/src/version.ts b/typescript/cli/src/version.ts index d4a8ed991b..8937a2bf5f 100644 --- a/typescript/cli/src/version.ts +++ b/typescript/cli/src/version.ts @@ -1 +1 @@ -export const VERSION = '5.6.0'; +export const VERSION = '5.6.1'; diff --git a/typescript/github-proxy/CHANGELOG.md b/typescript/github-proxy/CHANGELOG.md index a71264e351..ae6e2a41af 100644 --- a/typescript/github-proxy/CHANGELOG.md +++ b/typescript/github-proxy/CHANGELOG.md @@ -1,5 +1,7 @@ # @hyperlane-xyz/github-proxy +## 5.6.1 + ## 5.6.0 ## 5.5.0 diff --git a/typescript/github-proxy/package.json b/typescript/github-proxy/package.json index d8173c30da..9f804e01d2 100644 --- a/typescript/github-proxy/package.json +++ b/typescript/github-proxy/package.json @@ -1,7 +1,7 @@ { "name": "@hyperlane-xyz/github-proxy", "description": "Github proxy that adds the API key to requests", - "version": "5.6.0", + "version": "5.6.1", "private": true, "scripts": { "deploy": "wrangler deploy", diff --git a/typescript/helloworld/CHANGELOG.md b/typescript/helloworld/CHANGELOG.md index 1d2f1535be..17f77d3d57 100644 --- a/typescript/helloworld/CHANGELOG.md +++ b/typescript/helloworld/CHANGELOG.md @@ -1,5 +1,15 @@ # @hyperlane-xyz/helloworld +## 5.6.1 + +### Patch Changes + +- Updated dependencies [8cc0d9a4a] +- Updated dependencies [c55257cf5] +- Updated dependencies [8cc0d9a4a] + - @hyperlane-xyz/core@5.6.0 + - @hyperlane-xyz/sdk@5.6.1 + ## 5.6.0 ### Patch Changes diff --git a/typescript/helloworld/package.json b/typescript/helloworld/package.json index 927647d068..bc404b472b 100644 --- a/typescript/helloworld/package.json +++ b/typescript/helloworld/package.json @@ -1,11 +1,11 @@ { "name": "@hyperlane-xyz/helloworld", "description": "A basic skeleton of an Hyperlane app", - "version": "5.6.0", + "version": "5.6.1", "dependencies": { - "@hyperlane-xyz/core": "5.5.0", + "@hyperlane-xyz/core": "5.6.0", "@hyperlane-xyz/registry": "4.7.0", - "@hyperlane-xyz/sdk": "5.6.0", + "@hyperlane-xyz/sdk": "5.6.1", "@openzeppelin/contracts-upgradeable": "^4.9.3", "ethers": "^5.7.2" }, diff --git a/typescript/infra/CHANGELOG.md b/typescript/infra/CHANGELOG.md index 3b623135c5..f5f25b3f32 100644 --- a/typescript/infra/CHANGELOG.md +++ b/typescript/infra/CHANGELOG.md @@ -1,5 +1,13 @@ # @hyperlane-xyz/infra +## 5.6.1 + +### Patch Changes + +- @hyperlane-xyz/helloworld@5.6.1 +- @hyperlane-xyz/sdk@5.6.1 +- @hyperlane-xyz/utils@5.6.1 + ## 5.6.0 ### Minor Changes diff --git a/typescript/infra/package.json b/typescript/infra/package.json index 9d1d090c55..375d06c59d 100644 --- a/typescript/infra/package.json +++ b/typescript/infra/package.json @@ -1,7 +1,7 @@ { "name": "@hyperlane-xyz/infra", "description": "Infrastructure utilities for the Hyperlane Network", - "version": "5.6.0", + "version": "5.6.1", "dependencies": { "@arbitrum/sdk": "^3.0.0", "@aws-sdk/client-iam": "^3.74.0", @@ -13,10 +13,10 @@ "@ethersproject/hardware-wallets": "^5.7.0", "@ethersproject/providers": "^5.7.2", "@google-cloud/secret-manager": "^5.5.0", - "@hyperlane-xyz/helloworld": "5.6.0", + "@hyperlane-xyz/helloworld": "5.6.1", "@hyperlane-xyz/registry": "4.7.0", - "@hyperlane-xyz/sdk": "5.6.0", - "@hyperlane-xyz/utils": "5.6.0", + "@hyperlane-xyz/sdk": "5.6.1", + "@hyperlane-xyz/utils": "5.6.1", "@inquirer/prompts": "^5.3.8", "@nomiclabs/hardhat-etherscan": "^3.0.3", "@safe-global/api-kit": "1.3.0", diff --git a/typescript/sdk/CHANGELOG.md b/typescript/sdk/CHANGELOG.md index 0cec012ff1..ad33e87c26 100644 --- a/typescript/sdk/CHANGELOG.md +++ b/typescript/sdk/CHANGELOG.md @@ -1,5 +1,15 @@ # @hyperlane-xyz/sdk +## 5.6.1 + +### Patch Changes + +- Updated dependencies [8cc0d9a4a] +- Updated dependencies [c55257cf5] +- Updated dependencies [8cc0d9a4a] + - @hyperlane-xyz/core@5.6.0 + - @hyperlane-xyz/utils@5.6.1 + ## 5.6.0 ### Minor Changes diff --git a/typescript/sdk/package.json b/typescript/sdk/package.json index 1a5e86aa8f..44e35d35a4 100644 --- a/typescript/sdk/package.json +++ b/typescript/sdk/package.json @@ -1,14 +1,14 @@ { "name": "@hyperlane-xyz/sdk", "description": "The official SDK for the Hyperlane Network", - "version": "5.6.0", + "version": "5.6.1", "dependencies": { "@arbitrum/sdk": "^4.0.0", "@aws-sdk/client-s3": "^3.74.0", "@cosmjs/cosmwasm-stargate": "^0.32.4", "@cosmjs/stargate": "^0.32.4", - "@hyperlane-xyz/core": "5.5.0", - "@hyperlane-xyz/utils": "5.6.0", + "@hyperlane-xyz/core": "5.6.0", + "@hyperlane-xyz/utils": "5.6.1", "@safe-global/api-kit": "1.3.0", "@safe-global/protocol-kit": "1.3.0", "@safe-global/safe-deployments": "1.37.8", diff --git a/typescript/utils/CHANGELOG.md b/typescript/utils/CHANGELOG.md index e22dca07ab..8575e59fd8 100644 --- a/typescript/utils/CHANGELOG.md +++ b/typescript/utils/CHANGELOG.md @@ -1,5 +1,7 @@ # @hyperlane-xyz/utils +## 5.6.1 + ## 5.6.0 ### Minor Changes diff --git a/typescript/utils/package.json b/typescript/utils/package.json index 9baa234bf0..e06c4cf630 100644 --- a/typescript/utils/package.json +++ b/typescript/utils/package.json @@ -1,7 +1,7 @@ { "name": "@hyperlane-xyz/utils", "description": "General utilities and types for the Hyperlane network", - "version": "5.6.0", + "version": "5.6.1", "dependencies": { "@cosmjs/encoding": "^0.32.4", "@solana/web3.js": "^1.78.0", diff --git a/typescript/widgets/CHANGELOG.md b/typescript/widgets/CHANGELOG.md index e4698dd658..0ded083ed6 100644 --- a/typescript/widgets/CHANGELOG.md +++ b/typescript/widgets/CHANGELOG.md @@ -1,5 +1,12 @@ # @hyperlane-xyz/widgets +## 5.6.1 + +### Patch Changes + +- @hyperlane-xyz/sdk@5.6.1 +- @hyperlane-xyz/utils@5.6.1 + ## 5.6.0 ### Patch Changes diff --git a/typescript/widgets/package.json b/typescript/widgets/package.json index 031a874797..5160014c65 100644 --- a/typescript/widgets/package.json +++ b/typescript/widgets/package.json @@ -1,15 +1,15 @@ { "name": "@hyperlane-xyz/widgets", "description": "Common react components for Hyperlane projects", - "version": "5.6.0", + "version": "5.6.1", "peerDependencies": { "react": "^18", "react-dom": "^18" }, "dependencies": { "@headlessui/react": "^2.1.8", - "@hyperlane-xyz/sdk": "5.6.0", - "@hyperlane-xyz/utils": "5.6.0", + "@hyperlane-xyz/sdk": "5.6.1", + "@hyperlane-xyz/utils": "5.6.1", "clsx": "^2.1.1", "react-tooltip": "^5.28.0" }, diff --git a/yarn.lock b/yarn.lock index 40836ce5a2..279fe75e60 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7824,8 +7824,8 @@ __metadata: "@ethersproject/abi": "npm:*" "@ethersproject/providers": "npm:*" "@hyperlane-xyz/registry": "npm:4.7.0" - "@hyperlane-xyz/sdk": "npm:5.6.0" - "@hyperlane-xyz/utils": "npm:5.6.0" + "@hyperlane-xyz/sdk": "npm:5.6.1" + "@hyperlane-xyz/utils": "npm:5.6.1" "@inquirer/core": "npm:9.0.10" "@inquirer/figures": "npm:1.0.5" "@inquirer/prompts": "npm:^3.0.0" @@ -7860,13 +7860,13 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/core@npm:5.5.0, @hyperlane-xyz/core@workspace:solidity": +"@hyperlane-xyz/core@npm:5.6.0, @hyperlane-xyz/core@workspace:solidity": version: 0.0.0-use.local resolution: "@hyperlane-xyz/core@workspace:solidity" dependencies: "@arbitrum/nitro-contracts": "npm:^1.2.1" "@eth-optimism/contracts": "npm:^0.6.0" - "@hyperlane-xyz/utils": "npm:5.6.0" + "@hyperlane-xyz/utils": "npm:5.6.1" "@layerzerolabs/lz-evm-oapp-v2": "npm:2.0.2" "@layerzerolabs/solidity-examples": "npm:^1.1.0" "@nomiclabs/hardhat-ethers": "npm:^2.2.3" @@ -7917,13 +7917,13 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/helloworld@npm:5.6.0, @hyperlane-xyz/helloworld@workspace:typescript/helloworld": +"@hyperlane-xyz/helloworld@npm:5.6.1, @hyperlane-xyz/helloworld@workspace:typescript/helloworld": version: 0.0.0-use.local resolution: "@hyperlane-xyz/helloworld@workspace:typescript/helloworld" dependencies: - "@hyperlane-xyz/core": "npm:5.5.0" + "@hyperlane-xyz/core": "npm:5.6.0" "@hyperlane-xyz/registry": "npm:4.7.0" - "@hyperlane-xyz/sdk": "npm:5.6.0" + "@hyperlane-xyz/sdk": "npm:5.6.1" "@nomiclabs/hardhat-ethers": "npm:^2.2.3" "@nomiclabs/hardhat-waffle": "npm:^2.0.6" "@openzeppelin/contracts-upgradeable": "npm:^4.9.3" @@ -7970,10 +7970,10 @@ __metadata: "@ethersproject/hardware-wallets": "npm:^5.7.0" "@ethersproject/providers": "npm:^5.7.2" "@google-cloud/secret-manager": "npm:^5.5.0" - "@hyperlane-xyz/helloworld": "npm:5.6.0" + "@hyperlane-xyz/helloworld": "npm:5.6.1" "@hyperlane-xyz/registry": "npm:4.7.0" - "@hyperlane-xyz/sdk": "npm:5.6.0" - "@hyperlane-xyz/utils": "npm:5.6.0" + "@hyperlane-xyz/sdk": "npm:5.6.1" + "@hyperlane-xyz/utils": "npm:5.6.1" "@inquirer/prompts": "npm:^5.3.8" "@nomiclabs/hardhat-ethers": "npm:^2.2.3" "@nomiclabs/hardhat-etherscan": "npm:^3.0.3" @@ -8039,7 +8039,7 @@ __metadata: languageName: node linkType: hard -"@hyperlane-xyz/sdk@npm:5.6.0, @hyperlane-xyz/sdk@workspace:typescript/sdk": +"@hyperlane-xyz/sdk@npm:5.6.1, @hyperlane-xyz/sdk@workspace:typescript/sdk": version: 0.0.0-use.local resolution: "@hyperlane-xyz/sdk@workspace:typescript/sdk" dependencies: @@ -8047,8 +8047,8 @@ __metadata: "@aws-sdk/client-s3": "npm:^3.74.0" "@cosmjs/cosmwasm-stargate": "npm:^0.32.4" "@cosmjs/stargate": "npm:^0.32.4" - "@hyperlane-xyz/core": "npm:5.5.0" - "@hyperlane-xyz/utils": "npm:5.6.0" + "@hyperlane-xyz/core": "npm:5.6.0" + "@hyperlane-xyz/utils": "npm:5.6.1" "@nomiclabs/hardhat-ethers": "npm:^2.2.3" "@nomiclabs/hardhat-waffle": "npm:^2.0.6" "@safe-global/api-kit": "npm:1.3.0" @@ -8089,7 +8089,7 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/utils@npm:5.6.0, @hyperlane-xyz/utils@workspace:typescript/utils": +"@hyperlane-xyz/utils@npm:5.6.1, @hyperlane-xyz/utils@workspace:typescript/utils": version: 0.0.0-use.local resolution: "@hyperlane-xyz/utils@workspace:typescript/utils" dependencies: @@ -8115,8 +8115,8 @@ __metadata: dependencies: "@headlessui/react": "npm:^2.1.8" "@hyperlane-xyz/registry": "npm:4.7.0" - "@hyperlane-xyz/sdk": "npm:5.6.0" - "@hyperlane-xyz/utils": "npm:5.6.0" + "@hyperlane-xyz/sdk": "npm:5.6.1" + "@hyperlane-xyz/utils": "npm:5.6.1" "@storybook/addon-essentials": "npm:^7.6.14" "@storybook/addon-interactions": "npm:^7.6.14" "@storybook/addon-links": "npm:^7.6.14" From 15db5c112d90fb0c6a1035ed380be1ce63321af4 Mon Sep 17 00:00:00 2001 From: Trevor Porter Date: Thu, 24 Oct 2024 17:00:41 +0100 Subject: [PATCH 17/61] chore: update Eclipse validator sets to include Stride, minor cleanup (#4747) ### Description - deletes all the mainnet2 artifacts that we don't need - moves the sealevel helper scripts into more explicit directories, and ensures thresholds are appropriate - updates the eclipse ISM ### Drive-by changes ### Related issues ### Backward compatibility ### Testing --- .../sealevel/environments/mainnet2/.gitignore | 1 - .../environments/mainnet2/chain-config.json | 350 --------- .../mainnet2/gas-oracle-configs.json | 20 - .../hyperlane/helloworld-config.json | 30 - .../helloworld/hyperlane/program-ids.json | 42 -- .../helloworld/rc/helloworld-config.json | 32 - .../mainnet2/helloworld/rc/program-ids.json | 42 -- .../solana/hyperlane/multisig-config.json | 106 --- .../solana/nautilus/multisig-config.json | 18 - .../solana/nautilus/program-ids.json | 3 - .../solana/rc/multisig-config.json | 65 -- .../solana/rc/program-ids.json | 3 - .../mainnet2/solana/core/program-ids.json | 8 - .../mainnet2/warp-routes/zbc/program-ids.json | 10 - .../warp-routes/zbc/token-config.json | 16 - .../environments/mainnet3/chain-config.json | 705 +++++++++++++++--- .../hyperlane/multisig-config.json | 228 ++++-- .../scripts/print-multisig-ism-config.ts | 30 - .../print-chain-metadatas.ts | 4 +- .../print-multisig-ism-config.ts | 50 ++ 20 files changed, 819 insertions(+), 944 deletions(-) delete mode 100644 rust/sealevel/environments/mainnet2/.gitignore delete mode 100644 rust/sealevel/environments/mainnet2/chain-config.json delete mode 100644 rust/sealevel/environments/mainnet2/gas-oracle-configs.json delete mode 100644 rust/sealevel/environments/mainnet2/helloworld/hyperlane/helloworld-config.json delete mode 100644 rust/sealevel/environments/mainnet2/helloworld/hyperlane/program-ids.json delete mode 100644 rust/sealevel/environments/mainnet2/helloworld/rc/helloworld-config.json delete mode 100644 rust/sealevel/environments/mainnet2/helloworld/rc/program-ids.json delete mode 100644 rust/sealevel/environments/mainnet2/multisig-ism-message-id/solana/hyperlane/multisig-config.json delete mode 100644 rust/sealevel/environments/mainnet2/multisig-ism-message-id/solana/nautilus/multisig-config.json delete mode 100644 rust/sealevel/environments/mainnet2/multisig-ism-message-id/solana/nautilus/program-ids.json delete mode 100644 rust/sealevel/environments/mainnet2/multisig-ism-message-id/solana/rc/multisig-config.json delete mode 100644 rust/sealevel/environments/mainnet2/multisig-ism-message-id/solana/rc/program-ids.json delete mode 100644 rust/sealevel/environments/mainnet2/solana/core/program-ids.json delete mode 100644 rust/sealevel/environments/mainnet2/warp-routes/zbc/program-ids.json delete mode 100644 rust/sealevel/environments/mainnet2/warp-routes/zbc/token-config.json delete mode 100644 typescript/infra/scripts/print-multisig-ism-config.ts rename typescript/infra/scripts/{ => sealevel-helpers}/print-chain-metadatas.ts (87%) create mode 100644 typescript/infra/scripts/sealevel-helpers/print-multisig-ism-config.ts diff --git a/rust/sealevel/environments/mainnet2/.gitignore b/rust/sealevel/environments/mainnet2/.gitignore deleted file mode 100644 index 1b33b4a79d..0000000000 --- a/rust/sealevel/environments/mainnet2/.gitignore +++ /dev/null @@ -1 +0,0 @@ -**/**/keys diff --git a/rust/sealevel/environments/mainnet2/chain-config.json b/rust/sealevel/environments/mainnet2/chain-config.json deleted file mode 100644 index 23723ef823..0000000000 --- a/rust/sealevel/environments/mainnet2/chain-config.json +++ /dev/null @@ -1,350 +0,0 @@ -{ - "bsc": { - "chainId": 56, - "domainId": 56, - "name": "bsc", - "protocol": "ethereum", - "displayName": "Binance Smart Chain", - "displayNameShort": "Binance", - "nativeToken": { - "decimals": 18, - "name": "BNB", - "symbol": "BNB" - }, - "rpcUrls": [ - { - "http": "https://bsc-dataseed.binance.org" - }, - { - "http": "https://rpc.ankr.com/bsc" - } - ], - "blockExplorers": [ - { - "name": "BscScan", - "url": "https://bscscan.com", - "apiUrl": "https://api.bscscan.com/api", - "family": "etherscan" - } - ], - "blocks": { - "confirmations": 1, - "reorgPeriod": 15, - "estimateBlockTime": 3 - }, - "gasCurrencyCoinGeckoId": "binancecoin", - "gnosisSafeTransactionServiceUrl": "https://safe-transaction-bsc.safe.global/", - "transactionOverrides": { - "gasPrice": 7000000000 - } - }, - "avalanche": { - "chainId": 43114, - "domainId": 43114, - "name": "avalanche", - "protocol": "ethereum", - "displayName": "Avalanche", - "nativeToken": { - "decimals": 18, - "name": "Avalanche", - "symbol": "AVAX" - }, - "rpcUrls": [ - { - "http": "https://api.avax.network/ext/bc/C/rpc", - "pagination": { - "maxBlockRange": 100000, - "minBlockNumber": 6765067 - } - } - ], - "blockExplorers": [ - { - "name": "SnowTrace", - "url": "https://snowtrace.io", - "apiUrl": "https://api.snowtrace.io/api", - "family": "other" - } - ], - "blocks": { - "confirmations": 3, - "reorgPeriod": 3, - "estimateBlockTime": 2 - }, - "gasCurrencyCoinGeckoId": "avalanche-2", - "gnosisSafeTransactionServiceUrl": "https://safe-transaction-avalanche.safe.global/" - }, - "polygon": { - "chainId": 137, - "domainId": 137, - "name": "polygon", - "protocol": "ethereum", - "displayName": "Polygon", - "nativeToken": { - "name": "Ether", - "symbol": "ETH", - "decimals": 18 - }, - "rpcUrls": [ - { - "http": "https://rpc-mainnet.matic.quiknode.pro", - "pagination": { - "maxBlockRange": 10000, - "minBlockNumber": 19657100 - } - }, - { - "http": "https://polygon-rpc.com" - } - ], - "blockExplorers": [ - { - "name": "PolygonScan", - "url": "https://polygonscan.com", - "apiUrl": "https://api.polygonscan.com/api", - "family": "etherscan" - } - ], - "blocks": { - "confirmations": 3, - "reorgPeriod": 256, - "estimateBlockTime": 2 - }, - "gasCurrencyCoinGeckoId": "matic-network", - "gnosisSafeTransactionServiceUrl": "https://safe-transaction-polygon.safe.global/", - "transactionOverrides": { - "maxFeePerGas": 500000000000, - "maxPriorityFeePerGas": 100000000000 - } - }, - "celo": { - "chainId": 42220, - "domainId": 42220, - "name": "celo", - "protocol": "ethereum", - "displayName": "Celo", - "nativeToken": { - "decimals": 18, - "name": "CELO", - "symbol": "CELO" - }, - "rpcUrls": [ - { - "http": "https://forno.celo.org" - } - ], - "blockExplorers": [ - { - "name": "CeloScan", - "url": "https://celoscan.io", - "apiUrl": "https://api.celoscan.io/api", - "family": "etherscan" - }, - { - "name": "Blockscout", - "url": "https://explorer.celo.org", - "apiUrl": "https://explorer.celo.org/mainnet/api", - "family": "blockscout" - } - ], - "blocks": { - "confirmations": 1, - "reorgPeriod": 0, - "estimateBlockTime": 5 - }, - "gnosisSafeTransactionServiceUrl": "https://mainnet-tx-svc.celo-safe-prod.celo-networks-dev.org/" - }, - "arbitrum": { - "chainId": 42161, - "domainId": 42161, - "name": "arbitrum", - "protocol": "ethereum", - "displayName": "Arbitrum", - "nativeToken": { - "name": "Ether", - "symbol": "ETH", - "decimals": 18 - }, - "rpcUrls": [ - { - "http": "https://arb1.arbitrum.io/rpc" - } - ], - "blockExplorers": [ - { - "name": "Arbiscan", - "url": "https://arbiscan.io", - "apiUrl": "https://api.arbiscan.io/api", - "family": "etherscan" - } - ], - "blocks": { - "confirmations": 1, - "reorgPeriod": 0, - "estimateBlockTime": 3 - }, - "gasCurrencyCoinGeckoId": "ethereum", - "gnosisSafeTransactionServiceUrl": "https://safe-transaction-arbitrum.safe.global/" - }, - "optimism": { - "chainId": 10, - "domainId": 10, - "name": "optimism", - "protocol": "ethereum", - "displayName": "Optimism", - "nativeToken": { - "name": "Ether", - "symbol": "ETH", - "decimals": 18 - }, - "rpcUrls": [ - { - "http": "https://mainnet.optimism.io" - } - ], - "blockExplorers": [ - { - "name": "Etherscan", - "url": "https://optimistic.etherscan.io", - "apiUrl": "https://api-optimistic.etherscan.io/api", - "family": "etherscan" - } - ], - "blocks": { - "confirmations": 1, - "reorgPeriod": 0, - "estimateBlockTime": 3 - }, - "gasCurrencyCoinGeckoId": "ethereum", - "gnosisSafeTransactionServiceUrl": "https://safe-transaction-optimism.safe.global/" - }, - "ethereum": { - "chainId": 1, - "domainId": 1, - "name": "ethereum", - "protocol": "ethereum", - "displayName": "Ethereum", - "nativeToken": { - "name": "Ether", - "symbol": "ETH", - "decimals": 18 - }, - "rpcUrls": [ - { - "http": "https://mainnet.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161" - }, - { - "http": "https://cloudflare-eth.com" - } - ], - "blockExplorers": [ - { - "name": "Etherscan", - "url": "https://etherscan.io", - "apiUrl": "https://api.etherscan.io/api", - "family": "etherscan" - }, - { - "name": "Blockscout", - "url": "https://blockscout.com/eth/mainnet", - "apiUrl": "https://blockscout.com/eth/mainnet/api", - "family": "blockscout" - } - ], - "blocks": { - "confirmations": 3, - "reorgPeriod": 14, - "estimateBlockTime": 13 - }, - "gnosisSafeTransactionServiceUrl": "https://safe-transaction-mainnet.safe.global/", - "transactionOverrides": { - "maxFeePerGas": 150000000000, - "maxPriorityFeePerGas": 5000000000 - } - }, - "moonbeam": { - "chainId": 1284, - "domainId": 1284, - "name": "moonbeam", - "protocol": "ethereum", - "displayName": "Moonbeam", - "nativeToken": { - "decimals": 18, - "name": "GLMR", - "symbol": "GLMR" - }, - "rpcUrls": [ - { - "http": "https://rpc.api.moonbeam.network" - } - ], - "blockExplorers": [ - { - "name": "MoonScan", - "url": "https://moonscan.io", - "apiUrl": "https://api-moonbeam.moonscan.io/api", - "family": "etherscan" - } - ], - "blocks": { - "confirmations": 2, - "reorgPeriod": 2, - "estimateBlockTime": 12 - }, - "gnosisSafeTransactionServiceUrl": "https://transaction.multisig.moonbeam.network" - }, - "gnosis": { - "chainId": 100, - "domainId": 100, - "name": "gnosis", - "protocol": "ethereum", - "displayName": "Gnosis", - "nativeToken": { - "name": "xDai", - "symbol": "xDai", - "decimals": 18 - }, - "rpcUrls": [ - { - "http": "https://rpc.gnosischain.com", - "pagination": { - "maxBlockRange": 10000, - "minBlockNumber": 25997478 - } - } - ], - "blockExplorers": [ - { - "name": "GnosisScan", - "url": "https://gnosisscan.io", - "apiUrl": "https://api.gnosisscan.io/api", - "family": "etherscan" - } - ], - "blocks": { - "confirmations": 1, - "reorgPeriod": 14, - "estimateBlockTime": 5 - }, - "gasCurrencyCoinGeckoId": "xdai", - "gnosisSafeTransactionServiceUrl": "https://safe-transaction-gnosis-chain.safe.global/" - }, - "solanamainnet": { - "chainId": 1399811149, - "name": "solanamainnet", - "rpcUrls": [ - { - "http": "https://api.mainnet-beta.solana.com" - } - ] - }, - "nautilus": { - "chainId": 22222, - "name": "nautilus", - "rpcUrls": [ - { - "http": "https://api.nautilus.nautchain.xyz" - } - ] - } -} diff --git a/rust/sealevel/environments/mainnet2/gas-oracle-configs.json b/rust/sealevel/environments/mainnet2/gas-oracle-configs.json deleted file mode 100644 index 75bc47071a..0000000000 --- a/rust/sealevel/environments/mainnet2/gas-oracle-configs.json +++ /dev/null @@ -1,20 +0,0 @@ -[ - { - "domain": 56, - "gasOracle": { - "type": "remoteGasData", - "tokenExchangeRate": "100000000000000000000", - "gasPrice": "3000000000", - "tokenDecimals": 18 - } - }, - { - "domain": 22222, - "gasOracle": { - "type": "remoteGasData", - "tokenExchangeRate": "4700000000000000", - "gasPrice": "1000000000", - "tokenDecimals": 18 - } - } -] \ No newline at end of file diff --git a/rust/sealevel/environments/mainnet2/helloworld/hyperlane/helloworld-config.json b/rust/sealevel/environments/mainnet2/helloworld/hyperlane/helloworld-config.json deleted file mode 100644 index 34aaf62841..0000000000 --- a/rust/sealevel/environments/mainnet2/helloworld/hyperlane/helloworld-config.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "solanamainnet": {}, - "bsc": { - "foreignDeployment": "0xB97d3bF2fC296c2cAC4056bBC8A783ff39408e20" - }, - "avalanche": { - "foreignDeployment": "0x2A925CD8a5d919c5c6599633090c37fe38A561b6" - }, - "polygon": { - "foreignDeployment": "0x6c0aC8cEA75232aa7BeD8cbe9C4f820E7a77a9C3" - }, - "celo": { - "foreignDeployment": "0x4151773Db70C0b2D4c43Ea44A5FB5803ff1d3e0B" - }, - "arbitrum": { - "foreignDeployment": "0x96271cA0ab9eeFB3Ca481749c0Ca4c705fD4F523" - }, - "optimism": { - "foreignDeployment": "0xA6f0A37DFDe9C2c8F46F010989C47d9edB3a9FA8" - }, - "ethereum": { - "foreignDeployment": "0x9311cEE522A7C122B843b66cC31C6a63e2F92641" - }, - "moonbeam": { - "foreignDeployment": "0xAe067C08703508230357025B38c35Cd12793628c" - }, - "gnosis": { - "foreignDeployment": "0x26f32245fCF5Ad53159E875d5Cae62aEcf19c2d4" - } -} diff --git a/rust/sealevel/environments/mainnet2/helloworld/hyperlane/program-ids.json b/rust/sealevel/environments/mainnet2/helloworld/hyperlane/program-ids.json deleted file mode 100644 index 4666626386..0000000000 --- a/rust/sealevel/environments/mainnet2/helloworld/hyperlane/program-ids.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "moonbeam": { - "hex": "0x000000000000000000000000ae067c08703508230357025b38c35cd12793628c", - "base58": "1111111111113RcuHPfDctyAnFWHvj8tS1q8UHPh" - }, - "bsc": { - "hex": "0x000000000000000000000000b97d3bf2fc296c2cac4056bbc8a783ff39408e20", - "base58": "1111111111113atAoP8gQ2GYeue77ETUPAf8w9zw" - }, - "optimism": { - "hex": "0x000000000000000000000000a6f0a37dfde9c2c8f46f010989c47d9edb3a9fa8", - "base58": "1111111111113KtqevvpYv7NCiadmp6tRRfivB8K" - }, - "avalanche": { - "hex": "0x0000000000000000000000002a925cd8a5d919c5c6599633090c37fe38a561b6", - "base58": "111111111111bQB6b7XVDHSyvi7XmLrQMT8C3xH" - }, - "ethereum": { - "hex": "0x0000000000000000000000009311cee522a7c122b843b66cc31c6a63e2f92641", - "base58": "11111111111133qb6DzNiJ7whNaYGud2WqqtjxFS" - }, - "solanamainnet": { - "hex": "0x3797d0096b18b5b645c346a66d7f18c6c5738782c6bce24da57a3462bdef82b1", - "base58": "4k1gruSdH1r57V9QQK4aunzfMYzLFfF83jdYkkEwyem6" - }, - "celo": { - "hex": "0x0000000000000000000000004151773db70c0b2d4c43ea44a5fb5803ff1d3e0b", - "base58": "111111111111unDVQcjdeHntE83qvf1vsKCZ4av" - }, - "polygon": { - "hex": "0x0000000000000000000000006c0ac8cea75232aa7bed8cbe9c4f820e7a77a9c3", - "base58": "1111111111112WJXE3PCAsCXYZxU9Kh51sSZEa5G" - }, - "arbitrum": { - "hex": "0x00000000000000000000000096271ca0ab9eefb3ca481749c0ca4c705fd4f523", - "base58": "11111111111136L61X7cdT9tPZ4GKBtzJtrjFAd8" - }, - "gnosis": { - "hex": "0x00000000000000000000000026f32245fcf5ad53159e875d5cae62aecf19c2d4", - "base58": "111111111111YURfyMRiiTWy8X6pYHAqmYPmBpf" - } -} diff --git a/rust/sealevel/environments/mainnet2/helloworld/rc/helloworld-config.json b/rust/sealevel/environments/mainnet2/helloworld/rc/helloworld-config.json deleted file mode 100644 index 5e6ae772b4..0000000000 --- a/rust/sealevel/environments/mainnet2/helloworld/rc/helloworld-config.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "solanamainnet": { - "interchainSecurityModule": "BYTsxBuKVbwgsZFswzB91nrxveQySghwXzaKqn8exNnC" - }, - "gnosis": { - "foreignDeployment": "0x99ca8c74cE7Cfa9d72A51fbb05F9821f5f826b3a" - }, - "bsc": { - "foreignDeployment": "0xe5554478F167936dB253f79f57c41770bfa00Bae" - }, - "avalanche": { - "foreignDeployment": "0xe1De9910fe71cC216490AC7FCF019e13a34481D7" - }, - "polygon": { - "foreignDeployment": "0xAb65C41a1BC580a52f0b166879122EFdce0cB868" - }, - "celo": { - "foreignDeployment": "0xfE29f6a4468536029Fc9c97d3a9669b9fe38E114" - }, - "arbitrum": { - "foreignDeployment": "0x414B67F62b143d6db6E9b633168Dd6fd4DA20642" - }, - "optimism": { - "foreignDeployment": "0xB4caf2CA864B413DAA502fA18A8D48cD0740fC52" - }, - "ethereum": { - "foreignDeployment": "0xed31c20c5517EaC05decD5F6dCd01Fe6d16fD09D" - }, - "moonbeam": { - "foreignDeployment": "0x3eB9eE2CFC8DCB6F58B5869D33336CFcBf1dC354" - } -} diff --git a/rust/sealevel/environments/mainnet2/helloworld/rc/program-ids.json b/rust/sealevel/environments/mainnet2/helloworld/rc/program-ids.json deleted file mode 100644 index e3c0c46c41..0000000000 --- a/rust/sealevel/environments/mainnet2/helloworld/rc/program-ids.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "solanamainnet": { - "hex": "0x29dacc0e7124ea39b1fd43ab0fd30e038cf405c0229890229d0086d0b6516f9c", - "base58": "3pPDp16iVTJFge2sm85Q61hW61UN5xNqeG24gqFhzLFV" - }, - "avalanche": { - "hex": "0x000000000000000000000000e1de9910fe71cc216490ac7fcf019e13a34481d7", - "base58": "11111111111149We9K5tM8ijcyNy9zDMG9RyDBCJ" - }, - "arbitrum": { - "hex": "0x000000000000000000000000414b67f62b143d6db6e9b633168dd6fd4da20642", - "base58": "111111111111um79Yc6Evs5e1m2fdD2x7T1cpXb" - }, - "moonbeam": { - "hex": "0x0000000000000000000000003eb9ee2cfc8dcb6f58b5869d33336cfcbf1dc354", - "base58": "111111111111sgjzaeuHfqhExkdPQ1gJdhcSr4j" - }, - "optimism": { - "hex": "0x000000000000000000000000b4caf2ca864b413daa502fa18a8d48cd0740fc52", - "base58": "1111111111113X64nhkfMi9X5MbxKsiDTeeTmjsw" - }, - "ethereum": { - "hex": "0x000000000000000000000000ed31c20c5517eac05decd5f6dcd01fe6d16fd09d", - "base58": "1111111111114JfPmRiKEsR445qonVzCpsAvXCR2" - }, - "gnosis": { - "hex": "0x00000000000000000000000099ca8c74ce7cfa9d72a51fbb05f9821f5f826b3a", - "base58": "11111111111139Gc7eyQjpZrmWkkYQRyA2Grcvmf" - }, - "bsc": { - "hex": "0x000000000000000000000000e5554478f167936db253f79f57c41770bfa00bae", - "base58": "1111111111114CJxuV4VoAh5NsJy9qCGHqryoTCy" - }, - "polygon": { - "hex": "0x000000000000000000000000ab65c41a1bc580a52f0b166879122efdce0cb868", - "base58": "1111111111113PVkHAU9H7moDSoQvhC3Y2wgmovX" - }, - "celo": { - "hex": "0x000000000000000000000000fe29f6a4468536029fc9c97d3a9669b9fe38e114", - "base58": "1111111111114YNh3uhCWh2NjyPttobeNRyuDHYo" - } -} diff --git a/rust/sealevel/environments/mainnet2/multisig-ism-message-id/solana/hyperlane/multisig-config.json b/rust/sealevel/environments/mainnet2/multisig-ism-message-id/solana/hyperlane/multisig-config.json deleted file mode 100644 index 49cb69474f..0000000000 --- a/rust/sealevel/environments/mainnet2/multisig-ism-message-id/solana/hyperlane/multisig-config.json +++ /dev/null @@ -1,106 +0,0 @@ -{ - "celo": { - "type": 3, - "threshold": 4, - "validators": [ - "0x1f20274b1210046769d48174c2f0e7c25ca7d5c5", - "0x3bc014bafa43f93d534aed34f750997cdffcf007", - "0xd79d506d741fa735938f7b7847a926e34a6fe6b0", - "0xe4a258bc61e65914c2a477b2a8a433ab4ebdf44b", - "0x6aea63b0be4679c1385c26a92a3ff8aa6a8379f2", - "0xc0085e1a49bcc69e534272adb82c74c0e007e1ca" - ] - }, - "ethereum": { - "type": 3, - "threshold": 4, - "validators": [ - "0x4c327ccb881a7542be77500b2833dc84c839e7b7", - "0x84cb373148ef9112b277e68acf676fefa9a9a9a0", - "0x0d860c2b28bec3af4fd3a5997283e460ff6f2789", - "0xd4c1211f0eefb97a846c4e6d6589832e52fc03db", - "0x600c90404d5c9df885404d2cc5350c9b314ea3a2", - "0x892DC66F5B2f8C438E03f6323394e34A9C24F2D6" - ] - }, - "avalanche": { - "type": 3, - "threshold": 4, - "validators": [ - "0xa7aa52623fe3d78c343008c95894be669e218b8d", - "0xb6004433fb04f643e2d48ae765c0e7f890f0bc0c", - "0xa07e213e0985b21a6128e6c22ab5fb73948b0cc2", - "0x73853ed9a5f6f2e4c521970a94d43469e3cdaea6", - "0xbd2e136cda02ba627ca882e49b184cbe976081c8", - "0x1418126f944a44dad9edbab32294a8c890e7a9e3" - ] - }, - "polygon": { - "type": 3, - "threshold": 4, - "validators": [ - "0x59a001c3451e7f9f3b4759ea215382c1e9aa5fc1", - "0x009fb042d28944017177920c1d40da02bfebf474", - "0xba4b13e23705a5919c1901150d9697e8ffb3ea71", - "0x2faa4071b718972f9b4beec1d8cbaa4eb6cca6c6", - "0x5ae9b0f833dfe09ef455562a1f603f1634504dd6", - "0x6a163d312f7352a95c9b81dca15078d5bf77a442" - ] - }, - "bsc": { - "type": 3, - "threshold": 4, - "validators": [ - "0xcc84b1eb711e5076b2755cf4ad1d2b42c458a45e", - "0xefe34eae2bca1846b895d2d0762ec21796aa196a", - "0x662674e80e189b0861d6835c287693f50ee0c2ff", - "0x8a0f59075af466841808c529624807656309c9da", - "0xdd2ff046ccd748a456b4757a73d47f165469669f", - "0x034c4924c30ec4aa1b7f3ad58548988f0971e1bf" - ] - }, - "arbitrum": { - "type": 3, - "threshold": 4, - "validators": [ - "0xbcb815f38d481a5eba4d7ac4c9e74d9d0fc2a7e7", - "0xd839424e2e5ace0a81152298dc2b1e3bb3c7fb20", - "0xb8085c954b75b7088bcce69e61d12fcef797cd8d", - "0x9856dcb10fd6e5407fa74b5ab1d3b96cc193e9b7", - "0x505dff4e0827aa5065f5e001db888e0569d46490", - "0x25c6779d4610f940bf2488732e10bcffb9d36f81" - ] - }, - "optimism": { - "type": 3, - "threshold": 4, - "validators": [ - "0x9f2296d5cfc6b5176adc7716c7596898ded13d35", - "0x9c10bbe8efa03a8f49dfdb5c549258e3a8dca097", - "0x62144d4a52a0a0335ea5bb84392ef9912461d9dd", - "0xaff4718d5d637466ad07441ee3b7c4af8e328dbd", - "0xc64d1efeab8ae222bc889fe669f75d21b23005d9", - "0xfa174eb2b4921bb652bc1ada3e8b00e7e280bf3c" - ] - }, - "moonbeam": { - "type": 3, - "threshold": 3, - "validators": [ - "0x237243d32d10e3bdbbf8dbcccc98ad44c1c172ea", - "0x9509c8cf0a06955f27342262af501b74874e98fb", - "0xb7113c999e4d587b162dd1a28c73f3f51c6bdcdc", - "0x26725501597d47352a23cd26f122709f69ad53bc" - ] - }, - "gnosis": { - "type": 3, - "threshold": 3, - "validators": [ - "0xd0529ec8df08d0d63c0f023786bfa81e4bb51fd6", - "0x8a72ff8571c53c62c7ca02e8c97a443cd5674383", - "0x4075c2f6bd6d9562067cfe551d49c2bcafa7d692", - "0xa18580444eaeb1c5957e7b66a6bf84b6519f904d" - ] - } -} \ No newline at end of file diff --git a/rust/sealevel/environments/mainnet2/multisig-ism-message-id/solana/nautilus/multisig-config.json b/rust/sealevel/environments/mainnet2/multisig-ism-message-id/solana/nautilus/multisig-config.json deleted file mode 100644 index 5e08f27c0c..0000000000 --- a/rust/sealevel/environments/mainnet2/multisig-ism-message-id/solana/nautilus/multisig-config.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "bsc": { - "type": 3, - "threshold": 1, - "validators": [ - "0x0000000000000000000000000000000000000001" - ] - }, - "nautilus": { - "type": 3, - "threshold": 2, - "validators": [ - "0x9c920af9467595a23cb3433adefc3854d498a437", - "0x87611503e37ce041527c11c24263e8760fccf81f", - "0x573443248cf9929af0001b88f62131f2de29fe9f" - ] - } -} \ No newline at end of file diff --git a/rust/sealevel/environments/mainnet2/multisig-ism-message-id/solana/nautilus/program-ids.json b/rust/sealevel/environments/mainnet2/multisig-ism-message-id/solana/nautilus/program-ids.json deleted file mode 100644 index 23f0cdd550..0000000000 --- a/rust/sealevel/environments/mainnet2/multisig-ism-message-id/solana/nautilus/program-ids.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "program_id": "9k74DkJvS2x9QhG4XfnKsLkqaCDyVfaj8s6FyJyhAeEP" -} \ No newline at end of file diff --git a/rust/sealevel/environments/mainnet2/multisig-ism-message-id/solana/rc/multisig-config.json b/rust/sealevel/environments/mainnet2/multisig-ism-message-id/solana/rc/multisig-config.json deleted file mode 100644 index fc090e25db..0000000000 --- a/rust/sealevel/environments/mainnet2/multisig-ism-message-id/solana/rc/multisig-config.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "celo": { - "type": 3, - "threshold": 1, - "validators": [ - "0xe7a82e210f512f8e9900d6bc2acbf7981c63e66e" - ] - }, - "ethereum": { - "type": 3, - "threshold": 1, - "validators": [ - "0xaea1adb1c687b061e5b60b9da84cb69e7b5fab44" - ] - }, - "avalanche": { - "type": 3, - "threshold": 1, - "validators": [ - "0x706976391e23dea28152e0207936bd942aba01ce" - ] - }, - "polygon": { - "type": 3, - "threshold": 1, - "validators": [ - "0xef372f6ff7775989b3ac884506ee31c79638c989" - ] - }, - "bsc": { - "type": 3, - "threshold": 1, - "validators": [ - "0x0823081031a4a6f97c6083775c191d17ca96d0ab" - ] - }, - "arbitrum": { - "type": 3, - "threshold": 1, - "validators": [ - "0x1a95b35fb809d57faf1117c1cc29a6c5df289df1" - ] - }, - "optimism": { - "type": 3, - "threshold": 1, - "validators": [ - "0x60e938bf280bbc21bacfd8bf435459d9003a8f98" - ] - }, - "moonbeam": { - "type": 3, - "threshold": 1, - "validators": [ - "0x0df7140811e309dc69638352545151ebb9d5e0fd" - ] - }, - "gnosis": { - "type": 3, - "threshold": 1, - "validators": [ - "0x15f48e78092a4f79febface509cfd76467c6cdbb" - ] - } -} \ No newline at end of file diff --git a/rust/sealevel/environments/mainnet2/multisig-ism-message-id/solana/rc/program-ids.json b/rust/sealevel/environments/mainnet2/multisig-ism-message-id/solana/rc/program-ids.json deleted file mode 100644 index 0bf925699e..0000000000 --- a/rust/sealevel/environments/mainnet2/multisig-ism-message-id/solana/rc/program-ids.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "program_id": "BYTsxBuKVbwgsZFswzB91nrxveQySghwXzaKqn8exNnC" -} \ No newline at end of file diff --git a/rust/sealevel/environments/mainnet2/solana/core/program-ids.json b/rust/sealevel/environments/mainnet2/solana/core/program-ids.json deleted file mode 100644 index 53801da0ce..0000000000 --- a/rust/sealevel/environments/mainnet2/solana/core/program-ids.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "mailbox": "Ge9atjAc3Ltu91VTbNpJDCjZ9CFxFyck4h3YBcTF9XPq", - "validator_announce": "C88Lk5GR6cPxYoJxPbNDDEwsx5Kxn1wZEomvQ2So333g", - "multisig_ism_message_id": "6pHP4EeX2Xek24Be7PPTWCqcpmNEPENW1m9RnZSFSmA1", - "igp_program_id": "HksFWQM1EXJJ5mxo2uZoMfmksXHaNhCunh71NqcQQHZ8", - "overhead_igp_account": "GTj6WzNxLNFydq5zJrV9p13fyqotRoo1MQykNCWuVpbS", - "igp_account": "FCNfmLSZLo5x7oNYmkYU8WdPUu7pj636P9CaMxkmaCp7" -} \ No newline at end of file diff --git a/rust/sealevel/environments/mainnet2/warp-routes/zbc/program-ids.json b/rust/sealevel/environments/mainnet2/warp-routes/zbc/program-ids.json deleted file mode 100644 index 7d99aea92c..0000000000 --- a/rust/sealevel/environments/mainnet2/warp-routes/zbc/program-ids.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "solanamainnet": { - "hex": "0xc5ba229fa2822fe65ac2bd0a93d8371d75292c3415dd381923c1088a3308528b", - "base58": "EJqwFjvVJSAxH8Ur2PYuMfdvoJeutjmH6GkoEFQ4MdSa" - }, - "nautilus": { - "hex": "0x0000000000000000000000004501bbe6e731a4bc5c60c03a77435b2f6d5e9fe7", - "base58": "111111111111xm5qkrK7gZ8Cmjr4ggPLRxy2T8a" - } -} diff --git a/rust/sealevel/environments/mainnet2/warp-routes/zbc/token-config.json b/rust/sealevel/environments/mainnet2/warp-routes/zbc/token-config.json deleted file mode 100644 index 90b0e20cea..0000000000 --- a/rust/sealevel/environments/mainnet2/warp-routes/zbc/token-config.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "solanamainnet": { - "type": "collateral", - "decimals": 9, - "remoteDecimals": 9, - "token": "wzbcJyhGhQDLTV1S99apZiiBdE4jmYfbw99saMMdP59", - "splTokenProgram": "token", - "interchainSecurityModule": "9k74DkJvS2x9QhG4XfnKsLkqaCDyVfaj8s6FyJyhAeEP", - "owner": "EzppBFV2taxWw8kEjxNYvby6q7W1biJEqwP3iC7YgRe3" - }, - "nautilus": { - "type": "native", - "decimals": 18, - "foreignDeployment": "0x4501bBE6e731A4bC5c60C03A77435b2f6d5e9Fe7" - } -} diff --git a/rust/sealevel/environments/mainnet3/chain-config.json b/rust/sealevel/environments/mainnet3/chain-config.json index e21c91ccc9..b2b896b9a6 100644 --- a/rust/sealevel/environments/mainnet3/chain-config.json +++ b/rust/sealevel/environments/mainnet3/chain-config.json @@ -11,7 +11,7 @@ "blocks": { "confirmations": 1, "estimateBlockTime": 2, - "reorgPeriod": 0 + "reorgPeriod": 5 }, "chainId": 888888888, "deployer": { @@ -35,7 +35,50 @@ "http": "https://rpc.ancient8.gg" } ], - "technicalStack": "other" + "technicalStack": "opstack" + }, + "alephzeroevm": { + "blockExplorers": [ + { + "apiUrl": "https://evm-explorer.alephzero.org/api", + "family": "blockscout", + "name": "Aleph Zero Explorer", + "url": "https://evm-explorer.alephzero.org" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 3, + "reorgPeriod": 5 + }, + "chainId": 41455, + "deployer": { + "name": "Abacus Works", + "url": "https://www.hyperlane.xyz" + }, + "displayName": "Aleph Zero EVM", + "displayNameShort": "Aleph Zero EVM", + "domainId": 41455, + "gasCurrencyCoinGeckoId": "aleph-zero", + "index": { + "from": 3421962 + }, + "name": "alephzeroevm", + "nativeToken": { + "decimals": 18, + "name": "AZERO", + "symbol": "AZERO" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://rpc.alephzero.raas.gelato.cloud" + }, + { + "http": "https://alephzero.drpc.org" + } + ], + "technicalStack": "arbitrumnitro" }, "arbitrum": { "blockExplorers": [ @@ -49,7 +92,7 @@ "blocks": { "confirmations": 1, "estimateBlockTime": 3, - "reorgPeriod": 0 + "reorgPeriod": 5 }, "chainId": 42161, "deployer": { @@ -95,7 +138,7 @@ "blocks": { "confirmations": 1, "estimateBlockTime": 13, - "reorgPeriod": 0 + "reorgPeriod": 32 }, "chainId": 592, "deployer": { @@ -116,7 +159,8 @@ { "http": "https://evm.astar.network" } - ] + ], + "technicalStack": "polkadotsubstrate" }, "astarzkevm": { "blockExplorers": [ @@ -130,7 +174,7 @@ "blocks": { "confirmations": 1, "estimateBlockTime": 3, - "reorgPeriod": 0 + "reorgPeriod": 5 }, "chainId": 3776, "deployer": { @@ -154,7 +198,8 @@ { "http": "https://astar-zkevm-rpc.dwellir.com" } - ] + ], + "technicalStack": "polygoncdk" }, "avalanche": { "blockExplorers": [ @@ -197,7 +242,8 @@ "minBlockNumber": 6765067 } } - ] + ], + "technicalStack": "other" }, "base": { "blockExplorers": [ @@ -211,7 +257,7 @@ "blocks": { "confirmations": 1, "estimateBlockTime": 2, - "reorgPeriod": 1 + "reorgPeriod": 10 }, "chainId": 8453, "deployer": { @@ -239,7 +285,8 @@ { "http": "https://base.blockpi.network/v1/rpc/public" } - ] + ], + "technicalStack": "opstack" }, "bitlayer": { "blockExplorers": [ @@ -253,7 +300,7 @@ "blocks": { "confirmations": 1, "estimateBlockTime": 3, - "reorgPeriod": 0 + "reorgPeriod": 20 }, "chainId": 200901, "deployer": { @@ -280,7 +327,8 @@ { "http": "https://rpc.ankr.com/bitlayer" } - ] + ], + "technicalStack": "other" }, "blast": { "blockExplorers": [ @@ -294,7 +342,7 @@ "blocks": { "confirmations": 1, "estimateBlockTime": 2, - "reorgPeriod": 1 + "reorgPeriod": 5 }, "chainId": 81457, "deployer": { @@ -320,7 +368,7 @@ "http": "https://rpc.ankr.com/blast" } ], - "technicalStack": "other" + "technicalStack": "opstack" }, "bob": { "blockExplorers": [ @@ -334,7 +382,7 @@ "blocks": { "confirmations": 1, "estimateBlockTime": 2, - "reorgPeriod": 1 + "reorgPeriod": 5 }, "chainId": 60808, "deployer": { @@ -357,7 +405,7 @@ "http": "https://rpc.gobob.xyz" } ], - "technicalStack": "other" + "technicalStack": "opstack" }, "bsc": { "blockExplorers": [ @@ -401,6 +449,7 @@ "http": "https://bscrpc.com" } ], + "technicalStack": "other", "transactionOverrides": { "gasPrice": 3000000000 } @@ -445,21 +494,22 @@ { "http": "https://forno.celo.org" } - ] + ], + "technicalStack": "other" }, "cheesechain": { "blockExplorers": [ { - "apiUrl": "https://fetascan.io/api", + "apiUrl": "https://fetascan.xyz/api", "family": "blockscout", "name": "Fetascan", - "url": "https://fetascan.io" + "url": "https://fetascan.xyz" } ], "blocks": { "confirmations": 1, - "estimateBlockTime": 90, - "reorgPeriod": 0 + "estimateBlockTime": 30, + "reorgPeriod": 1 }, "chainId": 383353, "deployer": { @@ -487,6 +537,48 @@ ], "technicalStack": "arbitrumnitro" }, + "chiliz": { + "blockExplorers": [ + { + "apiUrl": "https://api.routescan.io/v2/network/mainnet/evm/88888/etherscan/api", + "family": "routescan", + "name": "Chiliscan", + "url": "https://chiliscan.com" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 3, + "reorgPeriod": 9 + }, + "chainId": 88888, + "deployer": { + "name": "Abacus Works", + "url": "https://www.hyperlane.xyz" + }, + "displayName": "Chiliz", + "domainId": 88888, + "gasCurrencyCoinGeckoId": "chiliz", + "name": "chiliz", + "nativeToken": { + "decimals": 18, + "name": "Chiliz", + "symbol": "CHZ" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://rpc.ankr.com/chiliz" + }, + { + "http": "https://chiliz.publicnode.com" + } + ], + "technicalStack": "other", + "transactionOverrides": { + "maxPriorityFeePerGas": 1000000000 + } + }, "coredao": { "blockExplorers": [ { @@ -499,7 +591,7 @@ "blocks": { "confirmations": 1, "estimateBlockTime": 3, - "reorgPeriod": 0 + "reorgPeriod": 21 }, "chainId": 1116, "deployer": { @@ -532,7 +624,8 @@ { "http": "https://rpc-core.icecreamswap.com" } - ] + ], + "technicalStack": "other" }, "cyber": { "blockExplorers": [ @@ -546,7 +639,7 @@ "blocks": { "confirmations": 1, "estimateBlockTime": 2, - "reorgPeriod": 0 + "reorgPeriod": 5 }, "chainId": 7560, "deployer": { @@ -570,7 +663,8 @@ { "http": "https://cyber.alt.technology" } - ] + ], + "technicalStack": "opstack" }, "degenchain": { "blockExplorers": [ @@ -584,7 +678,7 @@ "blocks": { "confirmations": 1, "estimateBlockTime": 10, - "reorgPeriod": 0 + "reorgPeriod": 5 }, "chainId": 666666666, "deployer": { @@ -623,7 +717,7 @@ "blocks": { "confirmations": 1, "estimateBlockTime": 2, - "reorgPeriod": 0 + "reorgPeriod": 5 }, "chainId": 2000, "deployer": { @@ -644,7 +738,8 @@ { "http": "https://rpc.dogechain.dog" } - ] + ], + "technicalStack": "polygoncdk" }, "eclipsemainnet": { "blockExplorers": [ @@ -679,7 +774,8 @@ { "http": "https://mainnetbeta-rpc.eclipse.xyz" } - ] + ], + "technicalStack": "other" }, "endurance": { "blockExplorers": [ @@ -693,7 +789,7 @@ "blocks": { "confirmations": 1, "estimateBlockTime": 12, - "reorgPeriod": 14 + "reorgPeriod": 15 }, "chainId": 648, "deployer": { @@ -715,7 +811,8 @@ { "http": "https://rpc-endurance.fusionist.io" } - ] + ], + "technicalStack": "other" }, "ethereum": { "blockExplorers": [ @@ -733,9 +830,9 @@ } ], "blocks": { - "confirmations": 3, + "confirmations": 2, "estimateBlockTime": 13, - "reorgPeriod": 14 + "reorgPeriod": 15 }, "chainId": 1, "deployer": { @@ -761,24 +858,21 @@ "http": "https://cloudflare-eth.com" } ], - "transactionOverrides": { - "maxFeePerGas": 150000000000, - "maxPriorityFeePerGas": 5000000000 - } + "technicalStack": "other" }, "everclear": { "blockExplorers": [ { - "apiUrl": "https://everclear.cloud.blockscout.com/api", + "apiUrl": "https://scan.everclear.org/api", "family": "blockscout", "name": "Everclear Explorer", - "url": "https://everclear.cloud.blockscout.com" + "url": "https://scan.everclear.org" } ], "blocks": { "confirmations": 1, "estimateBlockTime": 2, - "reorgPeriod": 0 + "reorgPeriod": 2 }, "chainId": 25327, "deployer": { @@ -817,7 +911,7 @@ "blocks": { "confirmations": 1, "estimateBlockTime": 2, - "reorgPeriod": 0 + "reorgPeriod": 3 }, "chainId": 14, "deployer": { @@ -847,7 +941,48 @@ { "http": "https://rpc.ankr.com/flare" } - ] + ], + "technicalStack": "other" + }, + "flow": { + "blockExplorers": [ + { + "apiUrl": "https://evm.flowscan.io/api", + "family": "blockscout", + "name": "EVM on Flow Explorer", + "url": "https://evm.flowscan.io" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 1, + "reorgPeriod": 25 + }, + "chainId": 747, + "deployer": { + "name": "Abacus Works", + "url": "https://www.hyperlane.xyz" + }, + "displayName": "EVM on Flow", + "domainId": 747, + "gasCurrencyCoinGeckoId": "flow", + "isTestnet": false, + "name": "flow", + "nativeToken": { + "decimals": 18, + "name": "Flow", + "symbol": "FLOW" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://mainnet.evm.nodes.onflow.org" + } + ], + "technicalStack": "other", + "transactionOverrides": { + "gasPrice": 100000000 + } }, "fraxtal": { "blockExplorers": [ @@ -861,7 +996,7 @@ "blocks": { "confirmations": 1, "estimateBlockTime": 2, - "reorgPeriod": 1 + "reorgPeriod": 5 }, "chainId": 252, "deployer": { @@ -886,7 +1021,8 @@ { "http": "https://fraxtal.drpc.org" } - ] + ], + "technicalStack": "opstack" }, "fusemainnet": { "blockExplorers": [ @@ -900,7 +1036,7 @@ "blocks": { "confirmations": 1, "estimateBlockTime": 5, - "reorgPeriod": 1 + "reorgPeriod": 19 }, "chainId": 122, "deployer": { @@ -931,7 +1067,8 @@ { "http": "https://fuse-pokt.nodies.app" } - ] + ], + "technicalStack": "other" }, "gnosis": { "blockExplorers": [ @@ -945,7 +1082,7 @@ "blocks": { "confirmations": 1, "estimateBlockTime": 5, - "reorgPeriod": 14 + "reorgPeriod": 5 }, "chainId": 100, "deployer": { @@ -971,7 +1108,51 @@ "minBlockNumber": 25997478 } } - ] + ], + "technicalStack": "other" + }, + "immutablezkevm": { + "blockExplorers": [ + { + "apiUrl": "https://explorer.immutable.com/api/eth-rpc", + "family": "blockscout", + "name": "Immutable Explorer", + "url": "https://explorer.immutable.com" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 2, + "reorgPeriod": 20 + }, + "chainId": 13371, + "deployer": { + "name": "Abacus Works", + "url": "https://www.hyperlane.xyz" + }, + "displayName": "Immutable zkEVM", + "domainId": 13371, + "gasCurrencyCoinGeckoId": "immutable-x", + "name": "immutablezkevm", + "nativeToken": { + "decimals": 18, + "name": "Immutable", + "symbol": "IMX" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://rpc.immutable.com" + }, + { + "http": "https://immutable.gateway.tenderly.co" + } + ], + "technicalStack": "other", + "transactionOverrides": { + "maxFeePerGas": 100000000000, + "maxPriorityFeePerGas": 100000000000 + } }, "inevm": { "blockExplorers": [ @@ -985,7 +1166,7 @@ "blocks": { "confirmations": 1, "estimateBlockTime": 3, - "reorgPeriod": 0 + "reorgPeriod": 3 }, "chainId": 2525, "deployer": { @@ -1055,7 +1236,8 @@ "http": "https://sentry.tm.injective.network:443" } ], - "slip44": 118 + "slip44": 118, + "technicalStack": "other" }, "kroma": { "blockExplorers": [ @@ -1069,7 +1251,7 @@ "blocks": { "confirmations": 1, "estimateBlockTime": 2, - "reorgPeriod": 0 + "reorgPeriod": 5 }, "chainId": 255, "deployer": { @@ -1096,7 +1278,8 @@ { "http": "https://1rpc.io/kroma" } - ] + ], + "technicalStack": "opstack" }, "linea": { "blockExplorers": [ @@ -1110,7 +1293,7 @@ "blocks": { "confirmations": 1, "estimateBlockTime": 3, - "reorgPeriod": 1 + "reorgPeriod": 5 }, "chainId": 59144, "deployer": { @@ -1141,7 +1324,8 @@ { "http": "https://linea.drpc.org" } - ] + ], + "technicalStack": "other" }, "lisk": { "blockExplorers": [ @@ -1155,7 +1339,7 @@ "blocks": { "confirmations": 3, "estimateBlockTime": 2, - "reorgPeriod": 0 + "reorgPeriod": 5 }, "chainId": 1135, "deployer": { @@ -1176,7 +1360,8 @@ { "http": "https://rpc.api.lisk.com" } - ] + ], + "technicalStack": "opstack" }, "lukso": { "blockExplorers": [ @@ -1190,7 +1375,7 @@ "blocks": { "confirmations": 1, "estimateBlockTime": 12, - "reorgPeriod": 14 + "reorgPeriod": 15 }, "chainId": 42, "deployer": { @@ -1217,7 +1402,47 @@ { "http": "https://42.rpc.thirdweb.com" } - ] + ], + "technicalStack": "other" + }, + "lumia": { + "blockExplorers": [ + { + "apiUrl": "https://explorer.lumia.org/api/eth-rpc", + "family": "blockscout", + "name": "Lumia Prism Explorer", + "url": "https://explorer.lumia.org" + } + ], + "blocks": { + "confirmations": 3, + "estimateBlockTime": 4, + "reorgPeriod": 5 + }, + "chainId": 994873017, + "deployer": { + "name": "Abacus Works", + "url": "https://www.hyperlane.xyz" + }, + "displayName": "Lumia Prism", + "domainId": 994873017, + "gasCurrencyCoinGeckoId": "orion-protocol", + "name": "lumia", + "nativeToken": { + "decimals": 18, + "name": "Lumia", + "symbol": "LUMIA" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://994873017.rpc.thirdweb.com" + }, + { + "http": "https://mainnet-rpc.lumia.org" + } + ], + "technicalStack": "polygoncdk" }, "mantapacific": { "blockExplorers": [ @@ -1231,7 +1456,7 @@ "blocks": { "confirmations": 1, "estimateBlockTime": 3, - "reorgPeriod": 1 + "reorgPeriod": 5 }, "chainId": 169, "deployer": { @@ -1258,7 +1483,8 @@ { "http": "https://manta.nirvanalabs.xyz/mantapublic" } - ] + ], + "technicalStack": "opstack" }, "mantle": { "blockExplorers": [ @@ -1272,7 +1498,7 @@ "blocks": { "confirmations": 3, "estimateBlockTime": 2, - "reorgPeriod": 1 + "reorgPeriod": 2 }, "chainId": 5000, "deployer": { @@ -1295,7 +1521,7 @@ "http": "https://rpc.mantle.xyz" } ], - "technicalStack": "other" + "technicalStack": "opstack" }, "merlin": { "blockExplorers": [ @@ -1309,7 +1535,7 @@ "blocks": { "confirmations": 1, "estimateBlockTime": 3, - "reorgPeriod": 0 + "reorgPeriod": 5 }, "chainId": 4200, "deployer": { @@ -1333,7 +1559,45 @@ { "http": "https://merlin.blockpi.network/v1/rpc/public" } - ] + ], + "technicalStack": "polygoncdk" + }, + "metall2": { + "blockExplorers": [ + { + "apiUrl": "https://explorer.metall2.com/api", + "family": "blockscout", + "name": "Metal L2 Explorer", + "url": "https://explorer.metall2.com" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 2, + "reorgPeriod": 5 + }, + "chainId": 1750, + "deployer": { + "name": "Abacus Works", + "url": "https://www.hyperlane.xyz" + }, + "displayName": "Metal L2", + "domainId": 1750, + "gasCurrencyCoinGeckoId": "ethereum", + "isTestnet": false, + "name": "metall2", + "nativeToken": { + "decimals": 18, + "name": "Ether", + "symbol": "ETH" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://rpc.metall2.com" + } + ], + "technicalStack": "opstack" }, "metis": { "blockExplorers": [ @@ -1347,7 +1611,7 @@ "blocks": { "confirmations": 1, "estimateBlockTime": 5, - "reorgPeriod": 0 + "reorgPeriod": 5 }, "chainId": 1088, "deployer": { @@ -1372,7 +1636,7 @@ "http": "https://andromeda.metis.io/?owner=1088" } ], - "technicalStack": "arbitrumnitro" + "technicalStack": "opstack" }, "mint": { "blockExplorers": [ @@ -1386,7 +1650,7 @@ "blocks": { "confirmations": 1, "estimateBlockTime": 2, - "reorgPeriod": 0 + "reorgPeriod": 5 }, "chainId": 185, "deployer": { @@ -1408,7 +1672,8 @@ { "http": "https://rpc.mintchain.io" } - ] + ], + "technicalStack": "opstack" }, "mode": { "blockExplorers": [ @@ -1422,7 +1687,7 @@ "blocks": { "confirmations": 1, "estimateBlockTime": 2, - "reorgPeriod": 1 + "reorgPeriod": 5 }, "chainId": 34443, "deployer": { @@ -1448,7 +1713,7 @@ "http": "https://mode.drpc.org" } ], - "technicalStack": "other" + "technicalStack": "opstack" }, "molten": { "blockExplorers": [ @@ -1461,7 +1726,7 @@ ], "blocks": { "confirmations": 1, - "estimateBlockTime": 1, + "estimateBlockTime": 30, "reorgPeriod": 0 }, "chainId": 360, @@ -1501,7 +1766,7 @@ "blocks": { "confirmations": 2, "estimateBlockTime": 12, - "reorgPeriod": 2 + "reorgPeriod": 10 }, "chainId": 1284, "deployer": { @@ -1524,6 +1789,7 @@ "http": "https://rpc.api.moonbeam.network" } ], + "technicalStack": "polkadotsubstrate", "transactionOverrides": { "maxFeePerGas": 350000000000, "maxPriorityFeePerGas": 50000000000 @@ -1580,6 +1846,7 @@ } ], "slip44": 118, + "technicalStack": "other", "transactionOverrides": { "gasPrice": "0.0075" } @@ -1617,7 +1884,8 @@ { "http": "https://mainnet-rpc.oortech.com" } - ] + ], + "technicalStack": "other" }, "optimism": { "blockExplorers": [ @@ -1631,7 +1899,7 @@ "blocks": { "confirmations": 1, "estimateBlockTime": 3, - "reorgPeriod": 0 + "reorgPeriod": 10 }, "chainId": 10, "deployer": { @@ -1653,7 +1921,8 @@ { "http": "https://mainnet.optimism.io" } - ] + ], + "technicalStack": "opstack" }, "osmosis": { "bech32Prefix": "osmo", @@ -1703,6 +1972,7 @@ } ], "slip44": 118, + "technicalStack": "other", "transactionOverrides": { "gasPrice": "0.025" } @@ -1748,8 +2018,9 @@ "http": "https://rpc.ankr.com/polygon" } ], + "technicalStack": "other", "transactionOverrides": { - "maxFeePerGas": 550000000000, + "maxFeePerGas": 800000000000, "maxPriorityFeePerGas": 50000000000 } }, @@ -1765,7 +2036,7 @@ "blocks": { "confirmations": 1, "estimateBlockTime": 10, - "reorgPeriod": 1 + "reorgPeriod": 5 }, "chainId": 1101, "deployer": { @@ -1792,10 +2063,47 @@ "http": "https://rpc.ankr.com/polygon_zkevm" } ], + "technicalStack": "polygoncdk", "transactionOverrides": { "gasPrice": 1000000000 } }, + "polynomial": { + "blockExplorers": [ + { + "apiUrl": "https://polynomialscan.io/api", + "family": "routescan", + "name": "Polynomial Explorer", + "url": "https://polynomialscan.io" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 2, + "reorgPeriod": 5 + }, + "chainId": 8008, + "deployer": { + "name": "Abacus Works", + "url": "https://www.hyperlane.xyz" + }, + "displayName": "Polynomial", + "domainId": 8008, + "gasCurrencyCoinGeckoId": "ethereum", + "name": "polynomial", + "nativeToken": { + "decimals": 18, + "name": "Ethereum", + "symbol": "ETH" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://rpc.polynomial.fi" + } + ], + "technicalStack": "opstack" + }, "proofofplay": { "blockExplorers": [ { @@ -1808,7 +2116,7 @@ "blocks": { "confirmations": 1, "estimateBlockTime": 1, - "reorgPeriod": 0 + "reorgPeriod": 5 }, "chainId": 70700, "deployer": { @@ -1835,6 +2143,45 @@ ], "technicalStack": "arbitrumnitro" }, + "rari": { + "blockExplorers": [ + { + "apiUrl": "https://mainnet.explorer.rarichain.org/api", + "family": "blockscout", + "name": "Rari Mainnet Explorer", + "url": "https://mainnet.explorer.rarichain.org" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 30, + "reorgPeriod": 0 + }, + "chainId": 1380012617, + "deployer": { + "name": "Abacus Works", + "url": "https://www.hyperlane.xyz" + }, + "displayName": "RARI Chain", + "domainId": 1380012617, + "gasCurrencyCoinGeckoId": "ethereum", + "index": { + "from": 541753 + }, + "name": "rari", + "nativeToken": { + "decimals": 18, + "name": "Ethereum", + "symbol": "ETH" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://mainnet.rpc.rarichain.org/http" + } + ], + "technicalStack": "arbitrumnitro" + }, "real": { "blockExplorers": [ { @@ -1846,7 +2193,7 @@ ], "blocks": { "confirmations": 1, - "estimateBlockTime": 4, + "estimateBlockTime": 30, "reorgPeriod": 0 }, "chainId": 111188, @@ -1886,7 +2233,7 @@ "blocks": { "confirmations": 1, "estimateBlockTime": 2, - "reorgPeriod": 0 + "reorgPeriod": 5 }, "chainId": 690, "deployer": { @@ -1908,7 +2255,53 @@ { "http": "https://rpc.redstonechain.com" } - ] + ], + "technicalStack": "opstack" + }, + "rootstock": { + "blockExplorers": [ + { + "apiUrl": "https://rootstock.blockscout.com/api", + "family": "blockscout", + "name": "Blockscout", + "url": "https://rootstock.blockscout.com" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 30, + "reorgPeriod": 4 + }, + "chainId": 30, + "deployer": { + "name": "Abacus Works", + "url": "https://www.hyperlane.xyz" + }, + "displayName": "Rootstock", + "domainId": 30, + "gasCurrencyCoinGeckoId": "rootstock", + "name": "rootstock", + "nativeToken": { + "decimals": 18, + "name": "Rootstock Smart Bitcoin", + "symbol": "RBTC" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://rpc.mainnet.rootstock.io/kXhXHf6TnnfW1POvr4UT0YUvujmuju-M" + }, + { + "http": "https://public-node.rsk.co" + }, + { + "http": "https://mycrypto.rsk.co" + } + ], + "technicalStack": "other", + "transactionOverrides": { + "gasPrice": 70000000 + } }, "sanko": { "blockExplorers": [ @@ -1921,8 +2314,8 @@ ], "blocks": { "confirmations": 1, - "estimateBlockTime": 10, - "reorgPeriod": 0 + "estimateBlockTime": 15, + "reorgPeriod": 1 }, "chainId": 1996, "deployer": { @@ -1961,7 +2354,7 @@ "blocks": { "confirmations": 1, "estimateBlockTime": 3, - "reorgPeriod": 30 + "reorgPeriod": 17 }, "chainId": 534352, "deployer": { @@ -1984,8 +2377,9 @@ "http": "https://scroll.blockpi.network/v1/rpc/public" } ], + "technicalStack": "other", "transactionOverrides": { - "gasPrice": 2000000000 + "gasPrice": 200000000 } }, "sei": { @@ -2023,6 +2417,7 @@ "http": "https://evm-rpc.sei-apis.com" } ], + "technicalStack": "other", "transactionOverrides": { "gasPrice": 101000000000 } @@ -2039,7 +2434,7 @@ "blocks": { "confirmations": 1, "estimateBlockTime": 5, - "reorgPeriod": 0 + "reorgPeriod": 5 }, "chainId": 109, "deployer": { @@ -2063,15 +2458,16 @@ { "http": "https://rpc.shibrpc.com" } - ] + ], + "technicalStack": "other" }, "solanamainnet": { "blockExplorers": [ { - "apiUrl": "https://explorer.solana.com?cluster=mainnet-beta", + "apiUrl": "https://solscan.io", "family": "other", "name": "Solana Explorer", - "url": "https://explorer.solana.com?cluster=mainnet-beta" + "url": "https://solscan.io" } ], "blocks": { @@ -2098,7 +2494,99 @@ { "http": "https://api.mainnet-beta.solana.com" } - ] + ], + "technicalStack": "other" + }, + "stride": { + "bech32Prefix": "stride", + "blockExplorers": [ + { + "apiUrl": "https://www.mintscan.io/stride", + "family": "other", + "name": "Mintscan", + "url": "https://www.mintscan.io/stride" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 5, + "reorgPeriod": 1 + }, + "chainId": "stride-1", + "deployer": { + "name": "Stride Labs", + "url": "https://www.stride.zone" + }, + "displayName": "Stride", + "domainId": 745, + "gasCurrencyCoinGeckoId": "stride", + "grpcUrls": [ + { + "http": "stride-grpc.polkachu.com:12290" + } + ], + "isTestnet": false, + "name": "stride", + "nativeToken": { + "decimals": 6, + "denom": "ustrd", + "name": "Stride", + "symbol": "STRD" + }, + "protocol": "cosmos", + "restUrls": [ + { + "http": "https://stride-api.polkachu.com" + } + ], + "rpcUrls": [ + { + "http": "https://stride-rpc.polkachu.com" + } + ], + "slip44": 118, + "transactionOverrides": { + "gasPrice": "0.0025" + } + }, + "superposition": { + "blockExplorers": [ + { + "apiUrl": "https://explorer.superposition.so/api", + "family": "blockscout", + "name": "Superposition Explorer", + "url": "https://explorer.superposition.so/" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 60, + "reorgPeriod": 0 + }, + "chainId": 55244, + "deployer": { + "name": "Abacus Works", + "url": "https://www.hyperlane.xyz" + }, + "displayName": "Superposition", + "domainId": 55244, + "gasCurrencyCoinGeckoId": "ethereum", + "index": { + "from": 1201 + }, + "name": "superposition", + "nativeToken": { + "decimals": 18, + "name": "Ethereum", + "symbol": "ETH" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://rpc.superposition.so" + } + ], + "technicalStack": "arbitrumnitro" }, "taiko": { "blockExplorers": [ @@ -2112,7 +2600,7 @@ "blocks": { "confirmations": 1, "estimateBlockTime": 12, - "reorgPeriod": 1 + "reorgPeriod": 5 }, "chainId": 167000, "deployer": { @@ -2149,7 +2637,7 @@ "blocks": { "confirmations": 1, "estimateBlockTime": 6, - "reorgPeriod": 0 + "reorgPeriod": 10 }, "chainId": 5845, "deployer": { @@ -2171,7 +2659,8 @@ { "http": "https://rpc.tangle.tools" } - ] + ], + "technicalStack": "polkadotsubstrate" }, "viction": { "blockExplorers": [ @@ -2185,7 +2674,7 @@ "blocks": { "confirmations": 1, "estimateBlockTime": 2, - "reorgPeriod": 0 + "reorgPeriod": 3 }, "chainId": 88, "deployer": { @@ -2209,7 +2698,8 @@ { "http": "https://viction.blockpi.network/v1/rpc/public" } - ] + ], + "technicalStack": "other" }, "worldchain": { "blockExplorers": [ @@ -2223,7 +2713,7 @@ "blocks": { "confirmations": 1, "estimateBlockTime": 2, - "reorgPeriod": 1 + "reorgPeriod": 5 }, "chainId": 480, "deployer": { @@ -2244,7 +2734,8 @@ { "http": "https://raas-backend.alchemy.com/rpc/worldchain-mainnet/rollup" } - ] + ], + "technicalStack": "opstack" }, "xai": { "blockExplorers": [ @@ -2258,7 +2749,7 @@ "blocks": { "confirmations": 1, "estimateBlockTime": 1, - "reorgPeriod": 0 + "reorgPeriod": 5 }, "chainId": 660279, "deployer": { @@ -2297,7 +2788,7 @@ "blocks": { "confirmations": 1, "estimateBlockTime": 10, - "reorgPeriod": 1 + "reorgPeriod": 5 }, "chainId": 196, "deployer": { @@ -2323,7 +2814,8 @@ { "http": "https://rpc.xlayer.tech" } - ] + ], + "technicalStack": "polygoncdk" }, "zetachain": { "blockExplorers": [ @@ -2362,7 +2854,8 @@ { "http": "https://zetachain-mainnet.g.allthatnode.com/archive/evm" } - ] + ], + "technicalStack": "other" }, "zircuit": { "blockExplorers": [ @@ -2376,7 +2869,7 @@ "blocks": { "confirmations": 1, "estimateBlockTime": 2, - "reorgPeriod": 0 + "reorgPeriod": 5 }, "chainId": 48900, "deployer": { @@ -2404,7 +2897,8 @@ { "http": "https://zircuit-mainnet.drpc.org" } - ] + ], + "technicalStack": "opstack" }, "zoramainnet": { "blockExplorers": [ @@ -2418,7 +2912,7 @@ "blocks": { "confirmations": 1, "estimateBlockTime": 2, - "reorgPeriod": 1 + "reorgPeriod": 5 }, "chainId": 7777777, "deployer": { @@ -2440,6 +2934,7 @@ { "http": "https://rpc.zora.energy" } - ] + ], + "technicalStack": "opstack" } } diff --git a/rust/sealevel/environments/mainnet3/multisig-ism-message-id/eclipsemainnet/hyperlane/multisig-config.json b/rust/sealevel/environments/mainnet3/multisig-ism-message-id/eclipsemainnet/hyperlane/multisig-config.json index 595a33b03c..2966c15b51 100644 --- a/rust/sealevel/environments/mainnet3/multisig-ism-message-id/eclipsemainnet/hyperlane/multisig-config.json +++ b/rust/sealevel/environments/mainnet3/multisig-ism-message-id/eclipsemainnet/hyperlane/multisig-config.json @@ -1,4 +1,13 @@ { + "alephzeroevm": { + "threshold": 2, + "validators": [ + "0xcae8fab142adc4e434bb7409e40dd932cc3851aa", + "0xCF0211faFBb91FD9D06D7E306B30032DC3A1934f", + "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" + ], + "type": "messageIdMultisigIsm" + }, "ancient8": { "threshold": 2, "validators": [ @@ -6,7 +15,7 @@ "0xa5a56e97fb46f0ac3a3d261e404acb998d9a6969", "0x95c7bf235837cb5a609fe6c95870410b9f68bcff" ], - "type": 3 + "type": "messageIdMultisigIsm" }, "arbitrum": { "threshold": 3, @@ -17,7 +26,7 @@ "0x38c7a4ca1273ead2e867d096adbcdd0e2acb21d8", "0xb3ac35d3988bca8c2ffd195b1c6bee18536b317b" ], - "type": 3 + "type": "messageIdMultisigIsm" }, "astar": { "threshold": 2, @@ -26,7 +35,7 @@ "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ], - "type": 3 + "type": "messageIdMultisigIsm" }, "astarzkevm": { "threshold": 2, @@ -35,7 +44,7 @@ "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ], - "type": 3 + "type": "messageIdMultisigIsm" }, "avalanche": { "threshold": 2, @@ -44,7 +53,7 @@ "0x402e0f8c6e4210d408b6ac00d197d4a099fcd25a", "0x38c7a4ca1273ead2e867d096adbcdd0e2acb21d8" ], - "type": 3 + "type": "messageIdMultisigIsm" }, "base": { "threshold": 3, @@ -55,7 +64,7 @@ "0xcff391b4e516452d424db66beb9052b041a9ed79", "0x5450447aee7b544c462c9352bef7cad049b0c2dc" ], - "type": 3 + "type": "messageIdMultisigIsm" }, "bitlayer": { "threshold": 2, @@ -64,7 +73,7 @@ "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ], - "type": 3 + "type": "messageIdMultisigIsm" }, "blast": { "threshold": 2, @@ -73,7 +82,7 @@ "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36", "0xae53467a5c2a9d9420c188d10fef5e1d9b9a5b80" ], - "type": 3 + "type": "messageIdMultisigIsm" }, "bob": { "threshold": 2, @@ -82,7 +91,7 @@ "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ], - "type": 3 + "type": "messageIdMultisigIsm" }, "bsc": { "threshold": 3, @@ -92,7 +101,7 @@ "0x38c7a4ca1273ead2e867d096adbcdd0e2acb21d8", "0x5450447aee7b544c462c9352bef7cad049b0c2dc" ], - "type": 3 + "type": "messageIdMultisigIsm" }, "celo": { "threshold": 3, @@ -103,7 +112,7 @@ "0xb3ac35d3988bca8c2ffd195b1c6bee18536b317b", "0x5450447aee7b544c462c9352bef7cad049b0c2dc" ], - "type": 3 + "type": "messageIdMultisigIsm" }, "cheesechain": { "threshold": 2, @@ -111,7 +120,16 @@ "0x478fb53c6860ae8fc35235ba0d38d49b13128226", "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f" ], - "type": 3 + "type": "messageIdMultisigIsm" + }, + "chiliz": { + "threshold": 2, + "validators": [ + "0x82d024f453b1a3f3f6606226f06b038da27596f3", + "0xCF0211faFBb91FD9D06D7E306B30032DC3A1934f", + "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" + ], + "type": "messageIdMultisigIsm" }, "coredao": { "threshold": 2, @@ -120,7 +138,7 @@ "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ], - "type": 3 + "type": "messageIdMultisigIsm" }, "cyber": { "threshold": 2, @@ -129,7 +147,7 @@ "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ], - "type": 3 + "type": "messageIdMultisigIsm" }, "degenchain": { "threshold": 2, @@ -138,7 +156,7 @@ "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ], - "type": 3 + "type": "messageIdMultisigIsm" }, "dogechain": { "threshold": 2, @@ -147,7 +165,7 @@ "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ], - "type": 3 + "type": "messageIdMultisigIsm" }, "endurance": { "threshold": 2, @@ -156,7 +174,7 @@ "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", "0x7419021c0de2772b763e554480158a82a291c1f2" ], - "type": 3 + "type": "messageIdMultisigIsm" }, "ethereum": { "threshold": 4, @@ -169,7 +187,7 @@ "0xb683b742b378632a5f73a2a5a45801b3489bba44", "0xbf1023eff3dba21263bf2db2add67a0d6bcda2de" ], - "type": 3 + "type": "messageIdMultisigIsm" }, "everclear": { "threshold": 2, @@ -178,7 +196,7 @@ "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", "0xD79DFbF56ee2268f061cc613027a44A880f61Ba2" ], - "type": 3 + "type": "messageIdMultisigIsm" }, "flare": { "threshold": 2, @@ -187,7 +205,16 @@ "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ], - "type": 3 + "type": "messageIdMultisigIsm" + }, + "flow": { + "threshold": 2, + "validators": [ + "0x3aee1090318e9c54d1d23194dcd0f2bee00ddc97", + "0xCF0211faFBb91FD9D06D7E306B30032DC3A1934f", + "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" + ], + "type": "messageIdMultisigIsm" }, "fraxtal": { "threshold": 2, @@ -196,7 +223,7 @@ "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", "0x25b3a88f7cfd3c9f7d7e32b295673a16a6ddbd91" ], - "type": 3 + "type": "messageIdMultisigIsm" }, "fusemainnet": { "threshold": 2, @@ -205,7 +232,7 @@ "0x6760226b34213d262D41D5291Ed57E81a68b4E0b", "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f" ], - "type": 3 + "type": "messageIdMultisigIsm" }, "gnosis": { "threshold": 3, @@ -215,7 +242,16 @@ "0x38c7a4ca1273ead2e867d096adbcdd0e2acb21d8", "0x5450447aee7b544c462c9352bef7cad049b0c2dc" ], - "type": 3 + "type": "messageIdMultisigIsm" + }, + "immutablezkevm": { + "threshold": 2, + "validators": [ + "0xa787c2952a4d22f776ee6e87e828e6f75de24330", + "0xCF0211faFBb91FD9D06D7E306B30032DC3A1934f", + "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" + ], + "type": "messageIdMultisigIsm" }, "inevm": { "threshold": 2, @@ -224,7 +260,7 @@ "0x6B1d09A97b813D53e9D4b7523DA36604C0B52242", "0x9ab11f38a609940153850df611c9a2175dcffe0f" ], - "type": 3 + "type": "messageIdMultisigIsm" }, "injective": { "threshold": 2, @@ -233,7 +269,7 @@ "0x6B1d09A97b813D53e9D4b7523DA36604C0B52242", "0x9e551b6694bbd295d7d6e6a2540c7d41ce70a3b9" ], - "type": 3 + "type": "messageIdMultisigIsm" }, "kroma": { "threshold": 2, @@ -242,7 +278,7 @@ "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ], - "type": 3 + "type": "messageIdMultisigIsm" }, "linea": { "threshold": 2, @@ -251,7 +287,7 @@ "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ], - "type": 3 + "type": "messageIdMultisigIsm" }, "lisk": { "threshold": 2, @@ -260,7 +296,7 @@ "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ], - "type": 3 + "type": "messageIdMultisigIsm" }, "lukso": { "threshold": 2, @@ -269,10 +305,19 @@ "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", "0x101cE77261245140A0871f9407d6233C8230Ec47" ], - "type": 3 + "type": "messageIdMultisigIsm" + }, + "lumia": { + "threshold": 2, + "validators": [ + "0x9e283254ed2cd2c80f007348c2822fc8e5c2fa5f", + "0xCF0211faFBb91FD9D06D7E306B30032DC3A1934f", + "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" + ], + "type": "messageIdMultisigIsm" }, "mantapacific": { - "threshold": 5, + "threshold": 4, "validators": [ "0x8e668c97ad76d0e28375275c41ece4972ab8a5bc", "0x521a3e6bf8d24809fde1c1fd3494a859a16f132c", @@ -282,7 +327,7 @@ "0xcc9a0b6de7fe314bd99223687d784730a75bb957", "0x42b6de2edbaa62c2ea2309ad85d20b3e37d38acf" ], - "type": 3 + "type": "messageIdMultisigIsm" }, "mantle": { "threshold": 2, @@ -291,7 +336,7 @@ "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ], - "type": 3 + "type": "messageIdMultisigIsm" }, "merlin": { "threshold": 2, @@ -300,7 +345,16 @@ "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ], - "type": 3 + "type": "messageIdMultisigIsm" + }, + "metall2": { + "threshold": 2, + "validators": [ + "0x1b000e1e1f0a032ed382c6d69a2d58f6fe773c09", + "0xCF0211faFBb91FD9D06D7E306B30032DC3A1934f", + "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" + ], + "type": "messageIdMultisigIsm" }, "metis": { "threshold": 2, @@ -309,7 +363,7 @@ "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ], - "type": 3 + "type": "messageIdMultisigIsm" }, "mint": { "threshold": 2, @@ -318,7 +372,7 @@ "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", "0x0230505530b80186f8cdccfaf9993eb97aebe98a" ], - "type": 3 + "type": "messageIdMultisigIsm" }, "mode": { "threshold": 3, @@ -328,7 +382,7 @@ "0x7e29608c6e5792bbf9128599ca309be0728af7b4", "0x101cE77261245140A0871f9407d6233C8230Ec47" ], - "type": 3 + "type": "messageIdMultisigIsm" }, "molten": { "threshold": 2, @@ -337,7 +391,7 @@ "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ], - "type": 3 + "type": "messageIdMultisigIsm" }, "moonbeam": { "threshold": 3, @@ -347,7 +401,7 @@ "0x38c7a4ca1273ead2e867d096adbcdd0e2acb21d8", "0xb3ac35d3988bca8c2ffd195b1c6bee18536b317b" ], - "type": 3 + "type": "messageIdMultisigIsm" }, "neutron": { "threshold": 4, @@ -360,7 +414,7 @@ "0x54b2cca5091b098a1a993dec03c4d1ee9af65999", "0x42b6de2edbaa62c2ea2309ad85d20b3e37d38acf" ], - "type": 3 + "type": "messageIdMultisigIsm" }, "oortmainnet": { "threshold": 2, @@ -369,7 +423,7 @@ "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36", "0x032dE4f94676bF9314331e7D83E8Db4aC74c9E21" ], - "type": 3 + "type": "messageIdMultisigIsm" }, "optimism": { "threshold": 3, @@ -380,14 +434,14 @@ "0xb3ac35d3988bca8c2ffd195b1c6bee18536b317b", "0x5450447aee7b544c462c9352bef7cad049b0c2dc" ], - "type": 3 + "type": "messageIdMultisigIsm" }, "osmosis": { "threshold": 1, "validators": [ "0xea483af11c19fa41b16c31d1534c2a486a92bcac" ], - "type": 3 + "type": "messageIdMultisigIsm" }, "polygon": { "threshold": 3, @@ -397,7 +451,7 @@ "0x38c7a4ca1273ead2e867d096adbcdd0e2acb21d8", "0x5450447aee7b544c462c9352bef7cad049b0c2dc" ], - "type": 3 + "type": "messageIdMultisigIsm" }, "polygonzkevm": { "threshold": 2, @@ -406,7 +460,16 @@ "0x865818fe1db986036d5fd0466dcd462562436d1a", "0x38c7a4ca1273ead2e867d096adbcdd0e2acb21d8" ], - "type": 3 + "type": "messageIdMultisigIsm" + }, + "polynomial": { + "threshold": 2, + "validators": [ + "0xa63ad0891e921ad5947d57e05831fabb9816eca7", + "0xCF0211faFBb91FD9D06D7E306B30032DC3A1934f", + "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" + ], + "type": "messageIdMultisigIsm" }, "proofofplay": { "threshold": 2, @@ -415,7 +478,16 @@ "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ], - "type": 3 + "type": "messageIdMultisigIsm" + }, + "rari": { + "threshold": 2, + "validators": [ + "0x989d6862e09de21337078efbd86843a3eb1133e3", + "0xCF0211faFBb91FD9D06D7E306B30032DC3A1934f", + "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" + ], + "type": "messageIdMultisigIsm" }, "real": { "threshold": 2, @@ -424,7 +496,7 @@ "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ], - "type": 3 + "type": "messageIdMultisigIsm" }, "redstone": { "threshold": 2, @@ -433,7 +505,16 @@ "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ], - "type": 3 + "type": "messageIdMultisigIsm" + }, + "rootstock": { + "threshold": 2, + "validators": [ + "0xcb8e3a72cf427feff27416d0e2ec375a052eaaee", + "0xCF0211faFBb91FD9D06D7E306B30032DC3A1934f", + "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" + ], + "type": "messageIdMultisigIsm" }, "sanko": { "threshold": 2, @@ -442,7 +523,7 @@ "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ], - "type": 3 + "type": "messageIdMultisigIsm" }, "scroll": { "threshold": 3, @@ -452,7 +533,7 @@ "0x38c7a4ca1273ead2e867d096adbcdd0e2acb21d8", "0xbac4ac39f1d8b5ef15f26fdb1294a7c9aba3f948" ], - "type": 3 + "type": "messageIdMultisigIsm" }, "sei": { "threshold": 3, @@ -462,7 +543,7 @@ "0x101cE77261245140A0871f9407d6233C8230Ec47", "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ], - "type": 3 + "type": "messageIdMultisigIsm" }, "shibarium": { "threshold": 2, @@ -471,7 +552,7 @@ "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ], - "type": 3 + "type": "messageIdMultisigIsm" }, "solanamainnet": { "threshold": 3, @@ -482,7 +563,32 @@ "0x38c7a4ca1273ead2e867d096adbcdd0e2acb21d8", "0xcb6bcbd0de155072a7ff486d9d7286b0f71dcc2d" ], - "type": 3 + "type": "messageIdMultisigIsm" + }, + "stride": { + "threshold": 4, + "validators": [ + "0x38c7a4ca1273ead2e867d096adbcdd0e2acb21d8", + "0x88f0E5528131b10e3463C4c68108217Dd33462ac", + "0xa3eaa1216827ad63dd9db43f6168258a89177990", + "0x3f869C36110F00D10dC74cca3ac1FB133cf019ad", + "0x502dC6135d16E74056f609FBAF76846814C197D3", + "0xc36979780c1aD43275182600a61Ce41f1C390FbE", + "0x87460dcEd16a75AECdBffD4189111d30B099f5b0", + "0xf54982134e52Eb7253236943FBffE0886C5bde0C", + "0x5937b7cE1029C3Ec4bD8e1AaCc0C0f9422654D7d", + "0x3a446ed2923c08445af06e53f0acb558c0e0413c" + ], + "type": "messageIdMultisigIsm" + }, + "superposition": { + "threshold": 2, + "validators": [ + "0x5978d0e6afa9270ddb87cff43a8fa7a763a5dfc4", + "0xCF0211faFBb91FD9D06D7E306B30032DC3A1934f", + "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" + ], + "type": "messageIdMultisigIsm" }, "taiko": { "threshold": 3, @@ -492,7 +598,7 @@ "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36", "0x2F007c82672F2Bb97227D4e3F80Ac481bfB40A2a" ], - "type": 3 + "type": "messageIdMultisigIsm" }, "tangle": { "threshold": 2, @@ -501,7 +607,7 @@ "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", "0xe271ef9a6e312540f099a378865432fa73f26689" ], - "type": 3 + "type": "messageIdMultisigIsm" }, "viction": { "threshold": 2, @@ -510,7 +616,7 @@ "0xa3f93fe365bf99f431d8fde740b140615e24f99b", "0x1f87c368f8e05a85ef9126d984a980a20930cb9c" ], - "type": 3 + "type": "messageIdMultisigIsm" }, "worldchain": { "threshold": 2, @@ -519,7 +625,7 @@ "0x11e2a683e83617f186614071e422b857256a9aae", "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f" ], - "type": 3 + "type": "messageIdMultisigIsm" }, "xai": { "threshold": 2, @@ -528,7 +634,7 @@ "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ], - "type": 3 + "type": "messageIdMultisigIsm" }, "xlayer": { "threshold": 2, @@ -537,7 +643,7 @@ "0xfed056cC0967F5BC9C6350F6C42eE97d3983394d", "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f" ], - "type": 3 + "type": "messageIdMultisigIsm" }, "zetachain": { "threshold": 3, @@ -547,7 +653,7 @@ "0x101cE77261245140A0871f9407d6233C8230Ec47", "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ], - "type": 3 + "type": "messageIdMultisigIsm" }, "zircuit": { "threshold": 3, @@ -557,7 +663,7 @@ "0x0180444c9342BD672867Df1432eb3dA354413a6E", "0x1da9176C2CE5cC7115340496fa7D1800a98911CE" ], - "type": 3 + "type": "messageIdMultisigIsm" }, "zoramainnet": { "threshold": 3, @@ -567,6 +673,6 @@ "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ], - "type": 3 + "type": "messageIdMultisigIsm" } } diff --git a/typescript/infra/scripts/print-multisig-ism-config.ts b/typescript/infra/scripts/print-multisig-ism-config.ts deleted file mode 100644 index 61ff67fa3b..0000000000 --- a/typescript/infra/scripts/print-multisig-ism-config.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { IsmType } from '@hyperlane-xyz/sdk'; - -import { multisigIsms } from '../config/multisigIsm.js'; -import { getChains } from '../config/registry.js'; - -import { getArgs, withContext } from './agent-utils.js'; - -// This script exists to print the default multisig ISM validator sets for a given environment -// so they can easily be copied into the Sealevel tooling. :'( - -async function main() { - const args = await withContext(getArgs()) - .describe('local', 'local chain') - .choices('local', getChains()) - .demandOption('local').argv; - - const config = multisigIsms( - args.environment, - args.local, - IsmType.MESSAGE_ID_MULTISIG, - args.context, - ); - - console.log(JSON.stringify(config, null, 2)); -} - -main().catch((err) => { - console.error(err); - process.exit(1); -}); diff --git a/typescript/infra/scripts/print-chain-metadatas.ts b/typescript/infra/scripts/sealevel-helpers/print-chain-metadatas.ts similarity index 87% rename from typescript/infra/scripts/print-chain-metadatas.ts rename to typescript/infra/scripts/sealevel-helpers/print-chain-metadatas.ts index ff8f771fe0..49473e5dd8 100644 --- a/typescript/infra/scripts/print-chain-metadatas.ts +++ b/typescript/infra/scripts/sealevel-helpers/print-chain-metadatas.ts @@ -1,7 +1,7 @@ import { pick } from '@hyperlane-xyz/utils'; -import { getArgs } from './agent-utils.js'; -import { getEnvironmentConfig } from './core-utils.js'; +import { getArgs } from '../agent-utils.js'; +import { getEnvironmentConfig } from '../core-utils.js'; // This script exists to print the chain metadata configs for a given environment // so they can easily be copied into the Sealevel tooling. :'( diff --git a/typescript/infra/scripts/sealevel-helpers/print-multisig-ism-config.ts b/typescript/infra/scripts/sealevel-helpers/print-multisig-ism-config.ts new file mode 100644 index 0000000000..69646ff013 --- /dev/null +++ b/typescript/infra/scripts/sealevel-helpers/print-multisig-ism-config.ts @@ -0,0 +1,50 @@ +import { IsmType } from '@hyperlane-xyz/sdk'; + +import { multisigIsms } from '../../config/multisigIsm.js'; +import { getChains } from '../../config/registry.js'; +import { getArgs, withContext } from '../agent-utils.js'; + +// This script exists to print the default multisig ISM validator sets for a given environment +// so they can easily be copied into the Sealevel tooling. :'( + +async function main() { + const args = await withContext(getArgs()) + .describe('local', 'local chain') + .choices('local', getChains()) + .demandOption('local').argv; + + const config = multisigIsms( + args.environment, + args.local, + IsmType.MESSAGE_ID_MULTISIG, + args.context, + ); + + // Cap any thresholds to 4 due to the Sealevel transaction size limit. + // Any higher than 4 at the moment will cause warp route synthetic deliveries to fail. + // Example message Solana -> Eclipse that mints a synthetic: + // https://explorer.eclipse.xyz/tx/3wcMvqZZjQon9o8nD49e3ci16AUJopZRLAfsAfs16ZrxgoNLoboNvrbV1hQHbnN3KXrWSqHmKnmM28mUvh5Un5Hd/inspect. + // At the time, the Solana threshold was 3. Taking the max tx size of 1232 and the tx's size 1121, + // we can find the number of additional signatures to be: floor((1232 - 1121)/65) = floor(1.707) = 1. + // So the total number of signatures is 3 + 1 = 4. + + const MAX_THRESHOLD = 4; + + for (const chain of Object.keys(config)) { + if (config[chain].threshold > MAX_THRESHOLD) { + console.warn( + `Threshold for ${chain} is ${config[chain].threshold}. Capping to ${MAX_THRESHOLD}.`, + ); + config[chain].threshold = MAX_THRESHOLD; + } + } + + console.warn; + + console.log(JSON.stringify(config, null, 2)); +} + +main().catch((err) => { + console.error(err); + process.exit(1); +}); From 89c60ab9c0ed7eeb5720aa1e06a5ab4029c6f482 Mon Sep 17 00:00:00 2001 From: Trevor Porter Date: Thu, 24 Oct 2024 17:10:41 +0100 Subject: [PATCH 18/61] feat: update Stride IGP to our own, temporarily whitelist our Stride deployment (#4748) ### Description - We have an IGP on Stride now. We expect not to fully index this till at least Friday - so for now we just whitelist the messages we care about. ### Drive-by changes ### Related issues ### Backward compatibility ### Testing --------- Co-authored-by: Daniel Savu <23065004+daniel-savu@users.noreply.github.com> --- .registryrc | 2 +- rust/main/config/mainnet_config.json | 2 +- typescript/infra/config/environments/mainnet3/agent.ts | 10 +++++++++- .../mainnet3/warp/eclipse-stride-TIA-addresses.json | 8 ++++++++ .../mainnet3/warp/eclipse-stride-stTIA-addresses.json | 8 ++++++++ 5 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 typescript/infra/config/environments/mainnet3/warp/eclipse-stride-TIA-addresses.json create mode 100644 typescript/infra/config/environments/mainnet3/warp/eclipse-stride-stTIA-addresses.json diff --git a/.registryrc b/.registryrc index a694b325bd..265b470930 100644 --- a/.registryrc +++ b/.registryrc @@ -1 +1 @@ -65b3c54ede7bf2b56280b913b8ed57bb08f52a36 +18666269e99ad8d2ba45579c4be783478e50e936 diff --git a/rust/main/config/mainnet_config.json b/rust/main/config/mainnet_config.json index 7f147b6e06..fee83dfbd6 100644 --- a/rust/main/config/mainnet_config.json +++ b/rust/main/config/mainnet_config.json @@ -4829,7 +4829,7 @@ } ], "slip44": 118, - "interchainGasPaymaster": "0x0000000000000000000000000000000000000000000000000000000000000000", + "interchainGasPaymaster": "0x602D5BF31F85FE90BF812A5EE6E2CC8CF6998A687125CABEDD120983CB88DA54", "mailbox": "0x89945750e089d84581f194e1947a58480b335f18386ad4f761f05feebf5e2454", "merkleTreeHook": "0x7ab4a8c3ba5371e34cd8d5dc584e0d924504fc21c3cbf41c3f64d436176bf007", "validatorAnnounce": "0xf57d954bf3ddb5f1032a0e020a99e931215cf83ceb4de987c781488065aaae0d", diff --git a/typescript/infra/config/environments/mainnet3/agent.ts b/typescript/infra/config/environments/mainnet3/agent.ts index fa21db977e..d6999cd18e 100644 --- a/typescript/infra/config/environments/mainnet3/agent.ts +++ b/typescript/infra/config/environments/mainnet3/agent.ts @@ -27,6 +27,8 @@ import { validatorChainConfig } from './validators.js'; import ancient8EthereumUsdcAddresses from './warp/ancient8-USDC-addresses.json'; import arbitrumTIAAddresses from './warp/arbitrum-TIA-addresses.json'; import arbitrumNeutronEclipAddresses from './warp/arbitrum-neutron-eclip-addresses.json'; +import eclipseStrideTiaAddresses from './warp/eclipse-stride-TIA-addresses.json'; +import eclipseStrideStTiaAddresses from './warp/eclipse-stride-stTIA-addresses.json'; import inevmEthereumUsdcAddresses from './warp/inevm-USDC-addresses.json'; import inevmEthereumUsdtAddresses from './warp/inevm-USDT-addresses.json'; import injectiveInevmInjAddresses from './warp/injective-inevm-addresses.json'; @@ -311,7 +313,13 @@ const gasPaymentEnforcement: GasPaymentEnforcement[] = [ // warp routes that we know are certainly paying for gas. { type: GasPaymentEnforcementPolicyType.None, - matchingList: [...routerMatchingList(injectiveInevmInjAddresses)], + matchingList: [ + ...routerMatchingList(injectiveInevmInjAddresses), + // As we are still indexing the IGP on Stride, temporarily whitelist + // Stride to Eclipse messages. + ...routerMatchingList(eclipseStrideTiaAddresses), + ...routerMatchingList(eclipseStrideStTiaAddresses), + ], }, { type: GasPaymentEnforcementPolicyType.OnChainFeeQuoting, diff --git a/typescript/infra/config/environments/mainnet3/warp/eclipse-stride-TIA-addresses.json b/typescript/infra/config/environments/mainnet3/warp/eclipse-stride-TIA-addresses.json new file mode 100644 index 0000000000..67cf44d89a --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/warp/eclipse-stride-TIA-addresses.json @@ -0,0 +1,8 @@ +{ + "eclipsemainnet": { + "router": "0xa0c167513f4d025217a48891973c3dbe41e10e76230033ef5d676299a18ca7f5" + }, + "stride": { + "router": "stride1pvtesu3ve7qn7ctll2x495mrqf2ysp6fws68grvcu6f7n2ajghgsh2jdj6" + } +} diff --git a/typescript/infra/config/environments/mainnet3/warp/eclipse-stride-stTIA-addresses.json b/typescript/infra/config/environments/mainnet3/warp/eclipse-stride-stTIA-addresses.json new file mode 100644 index 0000000000..538b9fe9a5 --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/warp/eclipse-stride-stTIA-addresses.json @@ -0,0 +1,8 @@ +{ + "eclipsemainnet": { + "router": "0x0d258188d0761163da174da890d0c1becdee51a01dbc9e2a6bfcb342140eb509" + }, + "stride": { + "router": "stride134axwdlam929m3mar3wv95nvkyep7mr87ravkqcpf8dfe3v0pjlqwrw6ee" + } +} From a42616ff3fc297d0fb218d143861a397a491a035 Mon Sep 17 00:00:00 2001 From: Kunal Arora <55632507+aroralanuk@users.noreply.github.com> Date: Fri, 25 Oct 2024 06:41:50 +0530 Subject: [PATCH 19/61] fix(contracts): add rebasing compatibility for `HypERC4626` (#4524) ### Description - Added overrides for transferFrom, totalSupply to reflect the internal share based accounting for the 4626 mirror asset ### Drive-by changes - Overridden `_transfer` to update the Transfer event to display the asset being transfers as amount not the internal shares. ### Related issues - fixes https://github.com/chainlight-io/2024-08-hyperlane/issues/6 ### Backward compatibility Yes ### Testing Fuzz testing --- .changeset/real-starfishes-fold.md | 5 + .../contracts/token/extensions/HypERC4626.sol | 95 +++++++++---- solidity/test/token/HypERC4626Test.t.sol | 133 ++++++++++++++++++ .../EvmERC20WarpRouteReader.hardhat-test.ts | 1 - typescript/sdk/src/token/deploy.ts | 4 +- typescript/sdk/src/token/schemas.ts | 7 +- 6 files changed, 212 insertions(+), 33 deletions(-) create mode 100644 .changeset/real-starfishes-fold.md diff --git a/.changeset/real-starfishes-fold.md b/.changeset/real-starfishes-fold.md new file mode 100644 index 0000000000..f161beae68 --- /dev/null +++ b/.changeset/real-starfishes-fold.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/core': patch +--- + +Added overrides for transferFrom, totalSupply to reflect the internal share based accounting for the 4626 mirror asset diff --git a/solidity/contracts/token/extensions/HypERC4626.sol b/solidity/contracts/token/extensions/HypERC4626.sol index 9ceb5536b2..7ad9c18421 100644 --- a/solidity/contracts/token/extensions/HypERC4626.sol +++ b/solidity/contracts/token/extensions/HypERC4626.sol @@ -22,10 +22,13 @@ import {TokenRouter} from "../libs/TokenRouter.sol"; // ============ External Imports ============ import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; /** * @title Hyperlane ERC20 Rebasing Token * @author Abacus Works + * @notice This contract implements a rebasing token that reflects yields from the origin chain */ contract HypERC4626 is HypERC20 { using Math for uint256; @@ -49,6 +52,67 @@ contract HypERC4626 is HypERC20 { _disableInitializers(); } + // ============ Public Functions ============ + + /// Override transfer to handle underlying amounts while using shares internally + /// @inheritdoc ERC20Upgradeable + /// @dev the Transfer event emitted from ERC20Upgradeable will be in terms of shares not assets, so it may be misleading + function transfer( + address to, + uint256 amount + ) public virtual override returns (bool) { + _transfer(_msgSender(), to, assetsToShares(amount)); + return true; + } + + /// Override transferFrom to handle underlying amounts while using shares internally + /// @inheritdoc ERC20Upgradeable + function transferFrom( + address sender, + address recipient, + uint256 amount + ) public virtual override returns (bool) { + address spender = _msgSender(); + uint256 shares = assetsToShares(amount); + _spendAllowance(sender, spender, amount); + _transfer(sender, recipient, shares); + return true; + } + + /// Override totalSupply to return the total assets instead of shares. This reflects the actual circulating supply in terms of assets, accounting for rebasing + /// @inheritdoc ERC20Upgradeable + function totalSupply() public view virtual override returns (uint256) { + return sharesToAssets(totalShares()); + } + + /// This returns the balance of the account in terms of assets, accounting for rebasing + /// @inheritdoc ERC20Upgradeable + function balanceOf( + address account + ) public view virtual override returns (uint256) { + return sharesToAssets(shareBalanceOf(account)); + } + + /// This function provides the total supply in terms of shares + function totalShares() public view returns (uint256) { + return super.totalSupply(); + } + + /// This returns the balance of the account in terms of shares + function shareBalanceOf(address account) public view returns (uint256) { + return super.balanceOf(account); + } + + function assetsToShares(uint256 _amount) public view returns (uint256) { + return _amount.mulDiv(PRECISION, exchangeRate); + } + + function sharesToAssets(uint256 _shares) public view returns (uint256) { + return _shares.mulDiv(exchangeRate, PRECISION); + } + + // ============ Internal Functions ============ + /// Override to send shares instead of assets from synthetic /// @inheritdoc TokenRouter function _transferRemote( @@ -78,6 +142,8 @@ contract HypERC4626 is HypERC20 { emit SentTransferRemote(_destination, _recipient, _amountOrId); } + /// override _handle to update exchange rate + /// @inheritdoc TokenRouter function _handle( uint32 _origin, bytes32 _sender, @@ -97,33 +163,4 @@ contract HypERC4626 is HypERC20 { } super._handle(_origin, _sender, _message); } - - // Override to send shares locally instead of assets - function transfer( - address to, - uint256 amount - ) public virtual override returns (bool) { - address owner = _msgSender(); - _transfer(owner, to, assetsToShares(amount)); - return true; - } - - function shareBalanceOf(address account) public view returns (uint256) { - return super.balanceOf(account); - } - - function balanceOf( - address account - ) public view virtual override returns (uint256) { - uint256 _balance = super.balanceOf(account); - return sharesToAssets(_balance); - } - - function assetsToShares(uint256 _amount) public view returns (uint256) { - return _amount.mulDiv(PRECISION, exchangeRate); - } - - function sharesToAssets(uint256 _shares) public view returns (uint256) { - return _shares.mulDiv(exchangeRate, PRECISION); - } } diff --git a/solidity/test/token/HypERC4626Test.t.sol b/solidity/test/token/HypERC4626Test.t.sol index ef92431489..d5aecb8e27 100644 --- a/solidity/test/token/HypERC4626Test.t.sol +++ b/solidity/test/token/HypERC4626Test.t.sol @@ -113,6 +113,11 @@ contract HypERC4626CollateralTest is HypTokenTest { _connectRouters(domains, addresses); } + function testDisableInitializers() public { + vm.expectRevert("Initializable: contract is already initialized"); + remoteToken.initialize(0, "", "", address(0), address(0), address(0)); + } + function test_collateralDomain() public view { assertEq( remoteRebasingToken.collateralDomain(), @@ -242,6 +247,108 @@ contract HypERC4626CollateralTest is HypTokenTest { ); } + function testTransferFrom() public { + _performRemoteTransferWithoutExpectation(0, transferAmount); + assertEq(remoteToken.balanceOf(BOB), transferAmount); + + uint256 transferAmount2 = 50e18; + vm.prank(BOB); + remoteToken.approve(CAROL, transferAmount2); + + vm.prank(CAROL); + bool success = remoteToken.transferFrom(BOB, DANIEL, transferAmount2); + assertTrue(success, "TransferFrom should succeed"); + + assertEq( + remoteToken.balanceOf(BOB), + transferAmount - transferAmount2, + "BOB's balance should decrease" + ); + assertEq( + remoteToken.balanceOf(DANIEL), + transferAmount2, + "DANIEL's balance should increase" + ); + assertEq( + remoteToken.allowance(BOB, CAROL), + 0, + "Allowance should be zero after transfer" + ); + } + + event Transfer(address indexed from, address indexed to, uint256 value); + + function testTransferEvent() public { + _performRemoteTransferWithoutExpectation(0, transferAmount); + assertEq(remoteToken.balanceOf(BOB), transferAmount); + + uint256 transferAmount2 = 50e18; + vm.expectEmit(true, true, false, true); + emit Transfer(BOB, CAROL, transferAmount2); + + vm.prank(BOB); + remoteToken.transfer(CAROL, transferAmount2); + + assertEq( + remoteToken.balanceOf(BOB), + transferAmount - transferAmount2, + "BOB's balance should decrease" + ); + assertEq( + remoteToken.balanceOf(CAROL), + transferAmount2, + "CAROL's balance should increase" + ); + } + + function testTotalShares() public { + uint256 initialShares = remoteRebasingToken.totalShares(); + assertEq(initialShares, 0, "Initial shares should be zero"); + + _performRemoteTransferWithoutExpectation(0, transferAmount); + uint256 sharesAfterTransfer = remoteRebasingToken.totalShares(); + assertEq( + sharesAfterTransfer, + remoteRebasingToken.assetsToShares(transferAmount), + "Shares should match transferred amount converted to shares" + ); + + _accrueYield(); + localRebasingToken.rebase(DESTINATION, bytes(""), address(0)); + remoteMailbox.processNextInboundMessage(); + + uint256 sharesAfterYield = remoteRebasingToken.totalShares(); + assertEq( + sharesAfterYield, + sharesAfterTransfer, + "Total shares should remain constant after yield accrual" + ); + } + + function testShareBalanceOf() public { + _performRemoteTransferWithoutExpectation(0, transferAmount); + + uint256 bobShareBalance = remoteRebasingToken.shareBalanceOf(BOB); + assertEq( + bobShareBalance, + remoteRebasingToken.assetsToShares(transferAmount), + "Bob's share balance should match transferred amount converted to shares" + ); + + _accrueYield(); + localRebasingToken.rebase(DESTINATION, bytes(""), address(0)); + remoteMailbox.processNextInboundMessage(); + + uint256 bobShareBalanceAfterYield = remoteRebasingToken.shareBalanceOf( + BOB + ); + assertEq( + bobShareBalanceAfterYield, + bobShareBalance, + "Bob's share balance should remain constant after yield accrual" + ); + } + function testWithdrawalWithoutYield() public { uint256 bobPrimaryBefore = primaryToken.balanceOf(BOB); _performRemoteTransferWithoutExpectation(0, transferAmount); @@ -480,6 +587,32 @@ contract HypERC4626CollateralTest is HypTokenTest { ); } + function testTotalSupply() public { + uint256 initialSupply = remoteToken.totalSupply(); + assertEq(initialSupply, 0, "Initial supply should be zero"); + + _performRemoteTransferWithoutExpectation(0, transferAmount); + uint256 supplyAfterTransfer = remoteToken.totalSupply(); + assertEq( + supplyAfterTransfer, + transferAmount, + "Supply should match transferred amount" + ); + + _accrueYield(); + localRebasingToken.rebase(DESTINATION, bytes(""), address(0)); + remoteMailbox.processNextInboundMessage(); + + uint256 supplyAfterYield = remoteToken.totalSupply(); + assertApproxEqRelDecimal( + supplyAfterYield, + transferAmount + _discountedYield(), + 1e14, + 0, + "Supply should include yield" + ); + } + function testTransfer_withHookSpecified( uint256, bytes calldata diff --git a/typescript/sdk/src/token/EvmERC20WarpRouteReader.hardhat-test.ts b/typescript/sdk/src/token/EvmERC20WarpRouteReader.hardhat-test.ts index c6795fc6c8..782acc3d28 100644 --- a/typescript/sdk/src/token/EvmERC20WarpRouteReader.hardhat-test.ts +++ b/typescript/sdk/src/token/EvmERC20WarpRouteReader.hardhat-test.ts @@ -174,7 +174,6 @@ describe('ERC20WarpRouterReader', async () => { name: TOKEN_NAME, symbol: TOKEN_NAME, decimals: TOKEN_DECIMALS, - totalSupply: TOKEN_SUPPLY, ...baseConfig, }, }; diff --git a/typescript/sdk/src/token/deploy.ts b/typescript/sdk/src/token/deploy.ts index ee2a9a399b..1c55191268 100644 --- a/typescript/sdk/src/token/deploy.ts +++ b/typescript/sdk/src/token/deploy.ts @@ -88,8 +88,10 @@ abstract class TokenDeployer< ]; if (isCollateralConfig(config) || isNativeConfig(config)) { return defaultArgs; - } else if (isSyntheticConfig(config) || isSyntheticRebaseConfig(config)) { + } else if (isSyntheticConfig(config)) { return [config.totalSupply, config.name, config.symbol, ...defaultArgs]; + } else if (isSyntheticRebaseConfig(config)) { + return [0, config.name, config.symbol, ...defaultArgs]; } else { throw new Error('Unknown collateral type when initializing arguments'); } diff --git a/typescript/sdk/src/token/schemas.ts b/typescript/sdk/src/token/schemas.ts index 1ef4a770b6..c92f98ea94 100644 --- a/typescript/sdk/src/token/schemas.ts +++ b/typescript/sdk/src/token/schemas.ts @@ -40,8 +40,11 @@ export const NativeConfigSchema = TokenMetadataSchema.partial().extend({ type: z.enum([TokenType.native, TokenType.nativeScaled]), }); -export const CollateralRebaseConfigSchema = - TokenMetadataSchema.partial().extend({ +export const CollateralRebaseConfigSchema = TokenMetadataSchema.omit({ + totalSupply: true, +}) + .partial() + .extend({ type: z.literal(TokenType.collateralVaultRebase), }); From 2a190ff61a3bb355f91b011c5d64ade5f50fdd28 Mon Sep 17 00:00:00 2001 From: Paul Balaji <10051819+paulbalaji@users.noreply.github.com> Date: Fri, 25 Oct 2024 12:43:45 +0100 Subject: [PATCH 20/61] chore: update agent image (#4745) ### Description chore: update agent image ### Drive-by changes ### Related issues ### Backward compatibility ### Testing infra --- typescript/infra/config/environments/mainnet3/agent.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/typescript/infra/config/environments/mainnet3/agent.ts b/typescript/infra/config/environments/mainnet3/agent.ts index d6999cd18e..68ac9ccb9a 100644 --- a/typescript/infra/config/environments/mainnet3/agent.ts +++ b/typescript/infra/config/environments/mainnet3/agent.ts @@ -429,7 +429,7 @@ const hyperlane: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: '42d6b50-20241021-155906', + tag: 'a64af8b-20241024-120818', }, gasPaymentEnforcement: gasPaymentEnforcement, metricAppContexts, @@ -438,7 +438,7 @@ const hyperlane: RootAgentConfig = { validators: { docker: { repo, - tag: 'efd438f-20241016-101828', + tag: 'a64af8b-20241024-120818', }, rpcConsensusType: RpcConsensusType.Quorum, chains: validatorChainConfig(Contexts.Hyperlane), @@ -448,7 +448,7 @@ const hyperlane: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: '42d6b50-20241021-155906', + tag: 'a64af8b-20241024-120818', }, resources: scraperResources, }, @@ -463,7 +463,7 @@ const releaseCandidate: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: '42d6b50-20241021-155906', + tag: 'a64af8b-20241024-120818', }, // We're temporarily (ab)using the RC relayer as a way to increase // message throughput. @@ -475,7 +475,7 @@ const releaseCandidate: RootAgentConfig = { validators: { docker: { repo, - tag: '9c056c7-20240911-154357', + tag: 'a64af8b-20241024-120818', }, rpcConsensusType: RpcConsensusType.Quorum, chains: validatorChainConfig(Contexts.ReleaseCandidate), From 14e4719f00be336cd7b7b74a342d212fe6a2c0a4 Mon Sep 17 00:00:00 2001 From: Danil Nemirovsky Date: Fri, 25 Oct 2024 13:51:44 +0100 Subject: [PATCH 21/61] feat: Use H512 as transaction hash in HyperlaneProvider and Scraper (#4759) ### Description Use H512 as transaction hash in HyperlaneProvider and Scraper. ### Related issues - Contributes https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/4272 ### Backward compatibility Yes ### Testing Manual run of Scraper for Ethereum and Neutron --------- Co-authored-by: Danil Nemirovsky <4614623+ameten@users.noreply.github.com> --- rust/main/Cargo.lock | 1 - rust/main/agents/scraper/Cargo.toml | 1 - .../agents/scraper/src/chain_scraper/mod.rs | 56 +++---- rust/main/agents/scraper/src/conversions.rs | 31 +--- rust/main/agents/scraper/src/db/block.rs | 3 +- rust/main/agents/scraper/src/db/message.rs | 5 +- rust/main/agents/scraper/src/db/payment.rs | 4 +- rust/main/agents/scraper/src/db/txn.rs | 20 ++- .../src/providers/cosmos/provider.rs | 32 ++-- .../src/rpc_clients/provider.rs | 4 +- .../chains/hyperlane-fuel/src/provider.rs | 12 +- .../chains/hyperlane-sealevel/src/provider.rs | 4 +- .../hyperlane-core/src/traits/provider.rs | 6 +- .../main/hyperlane-core/src/traits/signing.rs | 3 +- .../hyperlane-core/src/types/chain_data.rs | 4 +- .../hyperlane-core/src/types/conversions.rs | 138 ++++++++++++++++++ rust/main/hyperlane-core/src/types/mod.rs | 2 + rust/main/utils/hex/src/lib.rs | 38 ----- 18 files changed, 208 insertions(+), 156 deletions(-) create mode 100644 rust/main/hyperlane-core/src/types/conversions.rs diff --git a/rust/main/Cargo.lock b/rust/main/Cargo.lock index 87d2c813e9..27b11dcab1 100644 --- a/rust/main/Cargo.lock +++ b/rust/main/Cargo.lock @@ -7670,7 +7670,6 @@ dependencies = [ "ethers", "eyre", "futures", - "hex 0.1.0", "hyperlane-base", "hyperlane-core", "hyperlane-test", diff --git a/rust/main/agents/scraper/Cargo.toml b/rust/main/agents/scraper/Cargo.toml index 7bad1ec7eb..0b9e429b9b 100644 --- a/rust/main/agents/scraper/Cargo.toml +++ b/rust/main/agents/scraper/Cargo.toml @@ -29,7 +29,6 @@ tokio = { workspace = true, features = ["rt", "macros", "parking_lot"] } tracing-futures.workspace = true tracing.workspace = true -hex = { path = "../../utils/hex" } hyperlane-base = { path = "../../hyperlane-base" } hyperlane-core = { path = "../../hyperlane-core", features = ["agent"] } migration = { path = "migration" } diff --git a/rust/main/agents/scraper/src/chain_scraper/mod.rs b/rust/main/agents/scraper/src/chain_scraper/mod.rs index c23df70a16..0be5b2ef13 100644 --- a/rust/main/agents/scraper/src/chain_scraper/mod.rs +++ b/rust/main/agents/scraper/src/chain_scraper/mod.rs @@ -7,14 +7,15 @@ use std::{collections::HashMap, sync::Arc}; use async_trait::async_trait; use eyre::Result; +use itertools::Itertools; +use tracing::{trace, warn}; + use hyperlane_base::settings::IndexSettings; use hyperlane_core::{ unwrap_or_none_result, BlockId, BlockInfo, Delivery, HyperlaneDomain, HyperlaneLogStore, HyperlaneMessage, HyperlaneProvider, HyperlaneSequenceAwareIndexerStoreReader, - HyperlaneWatermarkedLogStore, Indexed, InterchainGasPayment, LogMeta, H256, + HyperlaneWatermarkedLogStore, Indexed, InterchainGasPayment, LogMeta, H256, H512, }; -use itertools::Itertools; -use tracing::{trace, warn}; use crate::db::{ BasicBlock, BlockCursor, ScraperDb, StorableDelivery, StorableMessage, StorablePayment, @@ -78,12 +79,10 @@ impl HyperlaneSqlDb { &self, log_meta: impl Iterator, ) -> Result> { - let block_id_by_txn_hash: HashMap = log_meta + let block_id_by_txn_hash: HashMap = log_meta .map(|meta| { ( - meta.transaction_id - .try_into() - .expect("256-bit transaction ids are the maximum supported at this time"), + meta.transaction_id, BlockId::new(meta.block_hash, meta.block_number), ) }) @@ -121,7 +120,7 @@ impl HyperlaneSqlDb { txns: impl Iterator, ) -> Result> { // mapping of txn hash to (txn_id, block_id). - let mut txns: HashMap, i64)> = txns + let mut txns: HashMap, i64)> = txns .map(|TxnWithBlockId { txn_hash, block_id }| (txn_hash, (None, block_id))) .collect(); @@ -145,9 +144,9 @@ impl HyperlaneSqlDb { let mut txns_to_fetch = txns.iter_mut().filter(|(_, id)| id.0.is_none()); let mut txns_to_insert: Vec = Vec::with_capacity(CHUNK_SIZE); - let mut hashes_to_insert: Vec<&H256> = Vec::with_capacity(CHUNK_SIZE); + let mut hashes_to_insert: Vec<&H512> = Vec::with_capacity(CHUNK_SIZE); - for mut chunk in as_chunks::<(&H256, &mut (Option, i64))>(txns_to_fetch, CHUNK_SIZE) { + for mut chunk in as_chunks::<(&H512, &mut (Option, i64))>(txns_to_fetch, CHUNK_SIZE) { for (hash, (_, block_id)) in chunk.iter() { let info = match self.provider.get_txn_by_hash(hash).await { Ok(info) => info, @@ -302,7 +301,7 @@ impl HyperlaneLogStore for HyperlaneSqlDb { if messages.is_empty() { return Ok(0); } - let txns: HashMap = self + let txns: HashMap = self .ensure_blocks_and_txns(messages.iter().map(|r| &r.1)) .await? .map(|t| (t.hash, t)) @@ -310,13 +309,8 @@ impl HyperlaneLogStore for HyperlaneSqlDb { let storable = messages .iter() .filter_map(|(message, meta)| { - txns.get( - &meta - .transaction_id - .try_into() - .expect("256-bit transaction ids are the maximum supported at this time"), - ) - .map(|t| (message.inner().clone(), meta, t.id)) + txns.get(&meta.transaction_id) + .map(|t| (message.inner().clone(), meta, t.id)) }) .map(|(msg, meta, txn_id)| StorableMessage { msg, meta, txn_id }); let stored = self @@ -336,7 +330,7 @@ impl HyperlaneLogStore for HyperlaneSqlDb { if deliveries.is_empty() { return Ok(0); } - let txns: HashMap = self + let txns: HashMap = self .ensure_blocks_and_txns(deliveries.iter().map(|r| &r.1)) .await? .map(|t| (t.hash, t)) @@ -344,13 +338,8 @@ impl HyperlaneLogStore for HyperlaneSqlDb { let storable = deliveries .iter() .filter_map(|(message_id, meta)| { - txns.get( - &meta - .transaction_id - .try_into() - .expect("256-bit transaction ids are the maximum supported at this time"), - ) - .map(|txn| (*message_id.inner(), meta, txn.id)) + txns.get(&meta.transaction_id) + .map(|txn| (*message_id.inner(), meta, txn.id)) }) .map(|(message_id, meta, txn_id)| StorableDelivery { message_id, @@ -378,7 +367,7 @@ impl HyperlaneLogStore for HyperlaneSqlDb { if payments.is_empty() { return Ok(0); } - let txns: HashMap = self + let txns: HashMap = self .ensure_blocks_and_txns(payments.iter().map(|r| &r.1)) .await? .map(|t| (t.hash, t)) @@ -386,13 +375,8 @@ impl HyperlaneLogStore for HyperlaneSqlDb { let storable = payments .iter() .filter_map(|(payment, meta)| { - txns.get( - &meta - .transaction_id - .try_into() - .expect("256-bit transaction ids are the maximum supported at this time"), - ) - .map(|txn| (payment.inner(), meta, txn.id)) + txns.get(&meta.transaction_id) + .map(|txn| (payment.inner(), meta, txn.id)) }) .map(|(payment, meta, txn_id)| StorablePayment { payment, @@ -446,13 +430,13 @@ where #[derive(Debug, Clone)] struct TxnWithId { - hash: H256, + hash: H512, id: i64, } #[derive(Debug, Clone)] struct TxnWithBlockId { - txn_hash: H256, + txn_hash: H512, block_id: i64, } diff --git a/rust/main/agents/scraper/src/conversions.rs b/rust/main/agents/scraper/src/conversions.rs index 84a64a8577..9252d5cd44 100644 --- a/rust/main/agents/scraper/src/conversions.rs +++ b/rust/main/agents/scraper/src/conversions.rs @@ -1,36 +1,7 @@ use num_bigint::{BigInt, Sign}; use sea_orm::prelude::BigDecimal; -use hyperlane_core::{H256, U256}; - -// Creates a big-endian hex representation of the address -pub fn address_to_bytes(data: &H256) -> Vec { - if hex::is_h160(data.as_fixed_bytes()) { - // take the last 20 bytes - data.as_fixed_bytes()[12..32].into() - } else { - h256_to_bytes(data) - } -} - -// Creates a big-endian hex representation of the address -pub fn bytes_to_address(data: Vec) -> eyre::Result { - if (data.len() != 20) && (data.len() != 32) { - return Err(eyre::eyre!("Invalid address length")); - } - if data.len() == 20 { - let mut prefix = vec![0; 12]; - prefix.extend(data); - Ok(H256::from_slice(&prefix[..])) - } else { - Ok(H256::from_slice(&data[..])) - } -} - -// Creates a big-endian hex representation of the address hash -pub fn h256_to_bytes(data: &H256) -> Vec { - data.as_fixed_bytes().as_slice().into() -} +use hyperlane_core::U256; pub fn u256_to_decimal(v: U256) -> BigDecimal { let mut buf = [0u8; 32]; diff --git a/rust/main/agents/scraper/src/db/block.rs b/rust/main/agents/scraper/src/db/block.rs index 1af05cd8c4..45ff0bf7c8 100644 --- a/rust/main/agents/scraper/src/db/block.rs +++ b/rust/main/agents/scraper/src/db/block.rs @@ -5,10 +5,9 @@ use sea_orm::{ }; use tracing::{debug, trace}; -use hyperlane_core::{BlockInfo, H256}; +use hyperlane_core::{address_to_bytes, h256_to_bytes, BlockInfo, H256}; use migration::OnConflict; -use crate::conversions::{address_to_bytes, h256_to_bytes}; use crate::date_time; use crate::db::ScraperDb; diff --git a/rust/main/agents/scraper/src/db/message.rs b/rust/main/agents/scraper/src/db/message.rs index f8b99fec50..423c24058a 100644 --- a/rust/main/agents/scraper/src/db/message.rs +++ b/rust/main/agents/scraper/src/db/message.rs @@ -5,10 +5,11 @@ use itertools::Itertools; use sea_orm::{prelude::*, ActiveValue::*, DeriveColumn, EnumIter, Insert, QuerySelect}; use tracing::{debug, instrument, trace}; -use hyperlane_core::{HyperlaneMessage, LogMeta, H256}; +use hyperlane_core::{ + address_to_bytes, bytes_to_address, h256_to_bytes, HyperlaneMessage, LogMeta, H256, +}; use migration::OnConflict; -use crate::conversions::{address_to_bytes, bytes_to_address, h256_to_bytes}; use crate::date_time; use crate::db::ScraperDb; diff --git a/rust/main/agents/scraper/src/db/payment.rs b/rust/main/agents/scraper/src/db/payment.rs index 250f4f3c6f..498128c34b 100644 --- a/rust/main/agents/scraper/src/db/payment.rs +++ b/rust/main/agents/scraper/src/db/payment.rs @@ -3,10 +3,10 @@ use itertools::Itertools; use sea_orm::{prelude::*, ActiveValue::*, Insert, QuerySelect}; use tracing::{debug, instrument, trace}; -use hyperlane_core::{InterchainGasPayment, LogMeta}; +use hyperlane_core::{h256_to_bytes, InterchainGasPayment, LogMeta}; use migration::OnConflict; -use crate::conversions::{h256_to_bytes, u256_to_decimal}; +use crate::conversions::u256_to_decimal; use crate::date_time; use crate::db::ScraperDb; diff --git a/rust/main/agents/scraper/src/db/txn.rs b/rust/main/agents/scraper/src/db/txn.rs index ff0e70f2b5..d5cec03dc7 100644 --- a/rust/main/agents/scraper/src/db/txn.rs +++ b/rust/main/agents/scraper/src/db/txn.rs @@ -2,19 +2,17 @@ use std::collections::HashMap; use derive_more::Deref; use eyre::{eyre, Context, Result}; -use hyperlane_core::{TxnInfo, H256}; use sea_orm::{ prelude::*, sea_query::OnConflict, ActiveValue::*, DeriveColumn, EnumIter, Insert, NotSet, QuerySelect, }; use tracing::{debug, instrument, trace}; +use hyperlane_core::{address_to_bytes, bytes_to_h512, h512_to_bytes, TxnInfo, H512}; + use super::generated::transaction; -use crate::{ - conversions::{address_to_bytes, h256_to_bytes, u256_to_decimal}, - date_time, - db::ScraperDb, -}; + +use crate::{conversions::u256_to_decimal, date_time, db::ScraperDb}; #[derive(Debug, Clone, Deref)] pub struct StorableTxn { @@ -43,8 +41,8 @@ impl ScraperDb { /// found be excluded from the hashmap. pub async fn get_txn_ids( &self, - hashes: impl Iterator, - ) -> Result> { + hashes: impl Iterator, + ) -> Result> { #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] enum QueryAs { Id, @@ -53,7 +51,7 @@ impl ScraperDb { // check database to see which txns we already know and fetch their IDs let txns = transaction::Entity::find() - .filter(transaction::Column::Hash.is_in(hashes.map(h256_to_bytes))) + .filter(transaction::Column::Hash.is_in(hashes.map(h512_to_bytes))) .select_only() .column_as(transaction::Column::Id, QueryAs::Id) .column_as(transaction::Column::Hash, QueryAs::Hash) @@ -62,7 +60,7 @@ impl ScraperDb { .await .context("When querying transactions")? .into_iter() - .map(|(id, hash)| Ok((H256::from_slice(&hash), id))) + .map(|(id, hash)| Ok((bytes_to_h512(&hash), id))) .collect::>>()?; trace!(?txns, "Queried transaction info for hashes"); @@ -86,7 +84,7 @@ impl ScraperDb { max_priority_fee_per_gas: Set(txn .max_priority_fee_per_gas .map(u256_to_decimal)), - hash: Unchanged(h256_to_bytes(&txn.hash)), + hash: Unchanged(h512_to_bytes(&txn.hash)), time_created: Set(date_time::now()), gas_used: Set(u256_to_decimal(receipt.gas_used)), gas_price: Set(txn.gas_price.map(u256_to_decimal)), diff --git a/rust/main/chains/hyperlane-cosmos/src/providers/cosmos/provider.rs b/rust/main/chains/hyperlane-cosmos/src/providers/cosmos/provider.rs index ef0c153458..e718df6102 100644 --- a/rust/main/chains/hyperlane-cosmos/src/providers/cosmos/provider.rs +++ b/rust/main/chains/hyperlane-cosmos/src/providers/cosmos/provider.rs @@ -17,9 +17,9 @@ use tracing::{error, warn}; use crypto::decompress_public_key; use hyperlane_core::{ - AccountAddressType, BlockInfo, ChainCommunicationError, ChainInfo, ChainResult, - ContractLocator, HyperlaneChain, HyperlaneDomain, HyperlaneProvider, HyperlaneProviderError, - TxnInfo, TxnReceiptInfo, H256, U256, + bytes_to_h512, h512_to_bytes, AccountAddressType, BlockInfo, ChainCommunicationError, + ChainInfo, ChainResult, ContractLocator, HyperlaneChain, HyperlaneDomain, HyperlaneProvider, + HyperlaneProviderError, TxnInfo, TxnReceiptInfo, H256, H512, U256, }; use crate::grpc::{WasmGrpcProvider, WasmProvider}; @@ -204,10 +204,10 @@ impl CosmosProvider { /// Extract contract address from transaction. fn contract(tx: &Tx, tx_hash: &H256) -> ChainResult { // We merge two error messages together so that both of them are reported - match Self::contract_address_from_msg_execute_contract(tx, tx_hash) { + match Self::contract_address_from_msg_execute_contract(tx) { Ok(contract) => Ok(contract), Err(msg_execute_contract_error) => { - match Self::contract_address_from_msg_recv_packet(tx, tx_hash) { + match Self::contract_address_from_msg_recv_packet(tx) { Ok(contract) => Ok(contract), Err(msg_recv_packet_error) => { let errors = vec![msg_execute_contract_error, msg_recv_packet_error]; @@ -221,10 +221,7 @@ impl CosmosProvider { } /// Assumes that there is only one `MsgExecuteContract` message in the transaction - fn contract_address_from_msg_execute_contract( - tx: &Tx, - tx_hash: &H256, - ) -> Result { + fn contract_address_from_msg_execute_contract(tx: &Tx) -> Result { use cosmrs::proto::cosmwasm::wasm::v1::MsgExecuteContract as ProtoMsgExecuteContract; let contract_execution_messages = tx @@ -253,10 +250,7 @@ impl CosmosProvider { Ok(contract) } - fn contract_address_from_msg_recv_packet( - tx: &Tx, - tx_hash: &H256, - ) -> Result { + fn contract_address_from_msg_recv_packet(tx: &Tx) -> Result { let packet_data = tx .body .messages @@ -392,7 +386,9 @@ impl HyperlaneProvider for CosmosProvider { Ok(block_info) } - async fn get_txn_by_hash(&self, hash: &H256) -> ChainResult { + async fn get_txn_by_hash(&self, hash: &H512) -> ChainResult { + let hash: H256 = H256::from_slice(&h512_to_bytes(hash)); + let tendermint_hash = Hash::from_bytes(Algorithm::Sha256, hash.as_bytes()) .expect("transaction hash should be of correct size"); @@ -400,7 +396,7 @@ impl HyperlaneProvider for CosmosProvider { let received_hash = H256::from_slice(response.hash.as_bytes()); - if &received_hash != hash { + if received_hash != hash { return Err(ChainCommunicationError::from_other_str(&format!( "received incorrect transaction, expected hash: {:?}, received hash: {:?}", hash, received_hash, @@ -409,12 +405,12 @@ impl HyperlaneProvider for CosmosProvider { let tx = Tx::from_bytes(&response.tx)?; - let contract = Self::contract(&tx, hash)?; + let contract = Self::contract(&tx, &hash)?; let (sender, nonce) = self.sender_and_nonce(&tx)?; - let gas_price = self.calculate_gas_price(hash, &tx); + let gas_price = self.calculate_gas_price(&hash, &tx); let tx_info = TxnInfo { - hash: hash.to_owned(), + hash: hash.into(), gas_limit: U256::from(response.tx_result.gas_wanted), max_priority_fee_per_gas: None, max_fee_per_gas: None, diff --git a/rust/main/chains/hyperlane-ethereum/src/rpc_clients/provider.rs b/rust/main/chains/hyperlane-ethereum/src/rpc_clients/provider.rs index c8ef3d78ce..d89cf9f43f 100644 --- a/rust/main/chains/hyperlane-ethereum/src/rpc_clients/provider.rs +++ b/rust/main/chains/hyperlane-ethereum/src/rpc_clients/provider.rs @@ -7,7 +7,7 @@ use async_trait::async_trait; use derive_new::new; use ethers::prelude::Middleware; use ethers_core::{abi::Address, types::BlockNumber}; -use hyperlane_core::{ethers_core_types, ChainInfo, HyperlaneCustomErrorWrapper, U256}; +use hyperlane_core::{ethers_core_types, ChainInfo, HyperlaneCustomErrorWrapper, H512, U256}; use tokio::time::sleep; use tracing::instrument; @@ -84,7 +84,7 @@ where #[instrument(err, skip(self))] #[allow(clippy::blocks_in_conditions)] // TODO: `rustc` 1.80.1 clippy issue - async fn get_txn_by_hash(&self, hash: &H256) -> ChainResult { + async fn get_txn_by_hash(&self, hash: &H512) -> ChainResult { let txn = get_with_retry_on_none( hash, |h| self.provider.get_transaction(*h), diff --git a/rust/main/chains/hyperlane-fuel/src/provider.rs b/rust/main/chains/hyperlane-fuel/src/provider.rs index a72f8a6c5f..cdd32650e1 100644 --- a/rust/main/chains/hyperlane-fuel/src/provider.rs +++ b/rust/main/chains/hyperlane-fuel/src/provider.rs @@ -17,9 +17,9 @@ use fuels::{ }; use futures::future::join_all; use hyperlane_core::{ - BlockInfo, ChainCommunicationError, ChainInfo, ChainResult, HyperlaneChain, HyperlaneDomain, - HyperlaneMessage, HyperlaneProvider, HyperlaneProviderError, Indexed, LogMeta, TxnInfo, H256, - H512, U256, + h512_to_bytes, BlockInfo, ChainCommunicationError, ChainInfo, ChainResult, HyperlaneChain, + HyperlaneDomain, HyperlaneMessage, HyperlaneProvider, HyperlaneProviderError, Indexed, LogMeta, + TxnInfo, H256, H512, U256, }; use crate::{make_client, make_provider, prelude::FuelIntoH256, ConnectionConf}; @@ -314,7 +314,9 @@ impl HyperlaneProvider for FuelProvider { /// Used by scraper #[allow(clippy::clone_on_copy)] // TODO: `rustc` 1.80.1 clippy issue #[allow(clippy::match_like_matches_macro)] // TODO: `rustc` 1.80.1 clippy issue - async fn get_txn_by_hash(&self, hash: &H256) -> ChainResult { + async fn get_txn_by_hash(&self, hash: &H512) -> ChainResult { + let hash = H256::from_slice(&h512_to_bytes(hash)); + let transaction_res = self .provider .get_transaction_by_id(&hash.0.into()) @@ -370,7 +372,7 @@ impl HyperlaneProvider for FuelProvider { }; Ok(TxnInfo { - hash: hash.clone(), + hash: hash.into(), gas_limit: gas_limit.into(), max_priority_fee_per_gas: None, max_fee_per_gas: None, diff --git a/rust/main/chains/hyperlane-sealevel/src/provider.rs b/rust/main/chains/hyperlane-sealevel/src/provider.rs index aaf32ac593..932a7191ea 100644 --- a/rust/main/chains/hyperlane-sealevel/src/provider.rs +++ b/rust/main/chains/hyperlane-sealevel/src/provider.rs @@ -3,7 +3,7 @@ use std::{str::FromStr, sync::Arc}; use async_trait::async_trait; use hyperlane_core::{ BlockInfo, ChainInfo, ChainResult, HyperlaneChain, HyperlaneDomain, HyperlaneProvider, - HyperlaneProviderError, TxnInfo, H256, U256, + HyperlaneProviderError, TxnInfo, H256, H512, U256, }; use solana_sdk::bs58; use solana_sdk::pubkey::Pubkey; @@ -68,7 +68,7 @@ impl HyperlaneProvider for SealevelProvider { Ok(block_info) } - async fn get_txn_by_hash(&self, _hash: &H256) -> ChainResult { + async fn get_txn_by_hash(&self, _hash: &H512) -> ChainResult { todo!() // FIXME } diff --git a/rust/main/hyperlane-core/src/traits/provider.rs b/rust/main/hyperlane-core/src/traits/provider.rs index 47070bc2ab..63b4d01dd9 100644 --- a/rust/main/hyperlane-core/src/traits/provider.rs +++ b/rust/main/hyperlane-core/src/traits/provider.rs @@ -4,7 +4,7 @@ use async_trait::async_trait; use auto_impl::auto_impl; use thiserror::Error; -use crate::{BlockInfo, ChainInfo, ChainResult, HyperlaneChain, TxnInfo, H256, U256}; +use crate::{BlockInfo, ChainInfo, ChainResult, HyperlaneChain, TxnInfo, H256, H512, U256}; /// Interface for a provider. Allows abstraction over different provider types /// for different chains. @@ -20,7 +20,7 @@ pub trait HyperlaneProvider: HyperlaneChain + Send + Sync + Debug { async fn get_block_by_height(&self, height: u64) -> ChainResult; /// Get txn info for a given txn hash - async fn get_txn_by_hash(&self, hash: &H256) -> ChainResult; + async fn get_txn_by_hash(&self, hash: &H512) -> ChainResult; /// Returns whether a contract exists at the provided address async fn is_contract(&self, address: &H256) -> ChainResult; @@ -40,7 +40,7 @@ pub enum HyperlaneProviderError { NoGasUsed, /// Could not find a transaction by hash #[error("Could not find transaction from provider with hash {0:?}")] - CouldNotFindTransactionByHash(H256), + CouldNotFindTransactionByHash(H512), /// Could not find a block by height #[error("Could not find block from provider with height {0:?}")] CouldNotFindBlockByHeight(u64), diff --git a/rust/main/hyperlane-core/src/traits/signing.rs b/rust/main/hyperlane-core/src/traits/signing.rs index 34e5676c42..5859d3c25b 100644 --- a/rust/main/hyperlane-core/src/traits/signing.rs +++ b/rust/main/hyperlane-core/src/traits/signing.rs @@ -1,10 +1,11 @@ +use std::fmt::{Debug, Formatter}; + use async_trait::async_trait; use auto_impl::auto_impl; use serde::{ ser::{SerializeStruct, Serializer}, Deserialize, Serialize, }; -use std::fmt::{Debug, Formatter}; use crate::utils::bytes_to_hex; use crate::{Signature, H160, H256}; diff --git a/rust/main/hyperlane-core/src/types/chain_data.rs b/rust/main/hyperlane-core/src/types/chain_data.rs index 5f5ecb2e3b..219f6989da 100644 --- a/rust/main/hyperlane-core/src/types/chain_data.rs +++ b/rust/main/hyperlane-core/src/types/chain_data.rs @@ -1,6 +1,6 @@ use derive_new::new; -use crate::{H256, U256}; +use crate::{H256, H512, U256}; /// Info about a given block in the chain. #[derive(Debug, Clone, Default)] @@ -27,7 +27,7 @@ pub struct ChainInfo { #[derive(Debug, Clone)] pub struct TxnInfo { /// Hash of this transaction - pub hash: H256, + pub hash: H512, /// Amount of gas which was allocated for running the transaction pub gas_limit: U256, /// Represents the maximum tx fee that will go to the miner as part of the diff --git a/rust/main/hyperlane-core/src/types/conversions.rs b/rust/main/hyperlane-core/src/types/conversions.rs new file mode 100644 index 0000000000..5336e0e2bf --- /dev/null +++ b/rust/main/hyperlane-core/src/types/conversions.rs @@ -0,0 +1,138 @@ +use uint::unroll; + +use crate::{H256, H512}; + +/// Creates a big-endian hex representation of the address +pub fn address_to_bytes(data: &H256) -> Vec { + if is_h160(data.as_fixed_bytes()) { + // take the last 20 bytes + data.as_fixed_bytes()[12..32].into() + } else { + h256_to_bytes(data) + } +} + +/// Creates a big-endian hex representation of the address +pub fn bytes_to_address(data: Vec) -> eyre::Result { + if (data.len() != 20) && (data.len() != 32) { + return Err(eyre::eyre!("Invalid address length")); + } + if data.len() == 20 { + let mut prefix = vec![0; 12]; + prefix.extend(data); + Ok(H256::from_slice(&prefix[..])) + } else { + Ok(H256::from_slice(&data[..])) + } +} + +/// Creates a big-endian hex representation of the address hash +pub fn h256_to_bytes(data: &H256) -> Vec { + data.as_fixed_bytes().as_slice().into() +} + +/// Creates a big-endian hex representation of the address hash +pub fn h512_to_bytes(data: &H512) -> Vec { + if is_h256(data.as_fixed_bytes()) { + // take the last 32 bytes + data.as_fixed_bytes()[32..64].into() + } else { + data.as_fixed_bytes().as_slice().into() + } +} + +/// Convert bytes into H512 with padding +pub fn bytes_to_h512(data: &[u8]) -> H512 { + assert!(data.len() <= 64); + + if data.len() == 64 { + return H512::from_slice(data); + } + + let mut buf = [0; 64]; + buf[64 - data.len()..64].copy_from_slice(data); + + H512::from_slice(&buf) +} + +/// Checks if a byte slice fits within 160 bits. Assumes a big-endian encoding; +/// ignores leading zeros. Current implementation only supports up to a 32 byte +/// array but this could easily be extended if needed. +pub const fn is_h160(data: &[u8; S]) -> bool { + assert!(S <= 32); + if S <= 20 { + true + } else { + let mut z = data[0]; + unroll! { + for i in 0..11 { + if S >= i + 22 { + z |= data[i + 1] + } + } + } + + z == 0 + } +} + +/// Checks if a byte slice fits within 32 bytes. Assumes a big-endian encoding; +/// ignores leading zeros. Current implementation only supports up to a 64 byte long +/// array but this could easily be extended if needed. +pub const fn is_h256(data: &[u8; S]) -> bool { + assert!(S <= 64); + if S <= 32 { + true + } else { + unroll! { + for i in 0..32 { + if data[i] != 0 { + return false; + } + } + } + + true + } +} + +#[cfg(test)] +mod test { + #[test] + fn is_h160() { + let v: [u8; 32] = [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfa, 0xd1, + 0xc9, 0x44, 0x69, 0x70, 0x08, 0x33, 0x71, 0x7f, 0xa8, 0xa3, 0x01, 0x72, 0x78, 0xbc, + 0x1c, 0xa8, 0x03, 0x1c, + ]; + assert!(super::is_h160(&v)); + + let v: [u8; 32] = [ + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfa, 0xd1, + 0xc9, 0x44, 0x69, 0x70, 0x08, 0x33, 0x71, 0x7f, 0xa8, 0xa3, 0x01, 0x72, 0x78, 0xbc, + 0x1c, 0xa8, 0x03, 0x1c, + ]; + assert!(!super::is_h160(&v)); + } + + #[test] + fn is_h256() { + let v: [u8; 64] = [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0xfa, 0xd1, + 0xc9, 0x44, 0x69, 0x70, 0x08, 0x33, 0x71, 0x7f, 0xa8, 0xa3, 0x01, 0x72, 0x78, 0xbc, + 0x1c, 0xa8, 0x03, 0x1c, 0x04, 0x1d, 0x05, 0x1e, + ]; + assert!(super::is_h256(&v)); + + let v: [u8; 64] = [ + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0xfa, 0xd1, + 0xc9, 0x44, 0x69, 0x70, 0x08, 0x33, 0x71, 0x7f, 0xa8, 0xa3, 0x01, 0x72, 0x78, 0xbc, + 0x1c, 0xa8, 0x03, 0x1c, 0x04, 0x1d, 0x05, 0x1e, + ]; + assert!(!super::is_h256(&v)); + } +} diff --git a/rust/main/hyperlane-core/src/types/mod.rs b/rust/main/hyperlane-core/src/types/mod.rs index a8973a72b3..84df52d18e 100644 --- a/rust/main/hyperlane-core/src/types/mod.rs +++ b/rust/main/hyperlane-core/src/types/mod.rs @@ -12,6 +12,7 @@ pub use announcement::*; pub use block_id::BlockId; pub use chain_data::*; pub use checkpoint::*; +pub use conversions::*; pub use indexing::*; pub use log_metadata::*; pub use merkle_tree::*; @@ -27,6 +28,7 @@ mod announcement; mod block_id; mod chain_data; mod checkpoint; +mod conversions; mod indexing; mod log_metadata; mod merkle_tree; diff --git a/rust/main/utils/hex/src/lib.rs b/rust/main/utils/hex/src/lib.rs index 46b4d2894f..719a0b7866 100644 --- a/rust/main/utils/hex/src/lib.rs +++ b/rust/main/utils/hex/src/lib.rs @@ -29,27 +29,6 @@ const FROM_HEX_CHARS: [u8; 256] = [ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, ]; -/// Checks if a byte slice fits within 160 bits. Assumes a big-endian encoding; -/// ignores leading zeros. Current implementation only supports up to a 32 byte -/// array but this could easily be extended if needed. -pub const fn is_h160(data: &[u8; S]) -> bool { - assert!(S <= 32); - if S <= 20 { - true - } else { - let mut z = data[0]; - unroll! { - for i in 0..11 { - if S >= i + 22 { - z |= data[i + 1] - } - } - } - - z == 0 - } -} - /// This formats a 160bit byte slice as a lowercase hex string without any /// prefixing (will include leading zeros). pub fn format_h160_raw(data: &[u8; 20]) -> String { @@ -150,23 +129,6 @@ impl Error for InvalidHexCharacter {} mod test { use crate::FROM_HEX_CHARS; - #[test] - fn is_h160() { - let v: [u8; 32] = [ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfa, 0xd1, - 0xc9, 0x44, 0x69, 0x70, 0x08, 0x33, 0x71, 0x7f, 0xa8, 0xa3, 0x01, 0x72, 0x78, 0xbc, - 0x1c, 0xa8, 0x03, 0x1c, - ]; - assert!(super::is_h160(&v)); - - let v: [u8; 32] = [ - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfa, 0xd1, - 0xc9, 0x44, 0x69, 0x70, 0x08, 0x33, 0x71, 0x7f, 0xa8, 0xa3, 0x01, 0x72, 0x78, 0xbc, - 0x1c, 0xa8, 0x03, 0x1c, - ]; - assert!(!super::is_h160(&v)); - } - #[test] fn format_h160() { let v: [u8; 20] = [ From 1bd8e3e384baa72bef1e1186f36590e220bf075e Mon Sep 17 00:00:00 2001 From: Daniel Savu <23065004+daniel-savu@users.noreply.github.com> Date: Fri, 25 Oct 2024 14:58:55 +0100 Subject: [PATCH 22/61] feat: Stride <> Eclipse TIA and stTIA (#4749) ### Description ### Drive-by changes ### Related issues ### Backward compatibility ### Testing --- .../gas-oracle-configs-eclipsemainnet.json | 43 +++++++++++-------- .../TIA-stride-eclipse/program-ids.json | 10 +++++ .../TIA-stride-eclipse/token-config.json | 17 ++++++++ .../stTIA-stride-eclipse/program-ids.json | 10 +++++ .../stTIA-stride-eclipse/token-config.json | 17 ++++++++ .../warp/STTIA-eclipsestride-deployments.yaml | 24 +++++++++++ .../warp/TIA-eclipsestride-deployments.yaml | 23 ++++++++++ .../getEclipseStrideSTTIAWarpConfig.ts | 29 +++++++++++++ .../getEclipseStrideTIAWarpConfig.ts | 29 +++++++++++++ .../environments/mainnet3/warp/warpIds.ts | 2 + typescript/infra/config/warp.ts | 4 ++ typescript/infra/src/warp/helm.ts | 2 +- 12 files changed, 192 insertions(+), 18 deletions(-) create mode 100644 rust/sealevel/environments/mainnet3/warp-routes/TIA-stride-eclipse/program-ids.json create mode 100644 rust/sealevel/environments/mainnet3/warp-routes/TIA-stride-eclipse/token-config.json create mode 100644 rust/sealevel/environments/mainnet3/warp-routes/stTIA-stride-eclipse/program-ids.json create mode 100644 rust/sealevel/environments/mainnet3/warp-routes/stTIA-stride-eclipse/token-config.json create mode 100644 typescript/infra/config/environments/mainnet3/warp/STTIA-eclipsestride-deployments.yaml create mode 100644 typescript/infra/config/environments/mainnet3/warp/TIA-eclipsestride-deployments.yaml create mode 100644 typescript/infra/config/environments/mainnet3/warp/configGetters/getEclipseStrideSTTIAWarpConfig.ts create mode 100644 typescript/infra/config/environments/mainnet3/warp/configGetters/getEclipseStrideTIAWarpConfig.ts diff --git a/rust/sealevel/environments/mainnet3/eclipsemainnet/gas-oracle-configs-eclipsemainnet.json b/rust/sealevel/environments/mainnet3/eclipsemainnet/gas-oracle-configs-eclipsemainnet.json index 95b108c96c..b459e88d4c 100644 --- a/rust/sealevel/environments/mainnet3/eclipsemainnet/gas-oracle-configs-eclipsemainnet.json +++ b/rust/sealevel/environments/mainnet3/eclipsemainnet/gas-oracle-configs-eclipsemainnet.json @@ -1,20 +1,29 @@ [ - { - "domain": 1, - "gasOracle": { - "type": "remoteGasData", - "tokenExchangeRate": "15000000000000000000", - "gasPrice": "10000000000", - "tokenDecimals": 18 - } - }, - { - "domain": 1399811149, - "gasOracle": { - "type": "remoteGasData", - "tokenExchangeRate": "887000000000000000", - "gasPrice": "15", - "tokenDecimals": 9 - } + { + "domain": 1, + "gasOracle": { + "type": "remoteGasData", + "tokenExchangeRate": "15000000000000000000", + "gasPrice": "10000000000", + "tokenDecimals": 18 } + }, + { + "domain": 1399811149, + "gasOracle": { + "type": "remoteGasData", + "tokenExchangeRate": "887000000000000000", + "gasPrice": "15", + "tokenDecimals": 9 + } + }, + { + "domain": 745, + "gasOracle": { + "type": "remoteGasData", + "tokenExchangeRate": "435246388284187", + "gasPrice": "7", + "tokenDecimals": 6 + } + } ] diff --git a/rust/sealevel/environments/mainnet3/warp-routes/TIA-stride-eclipse/program-ids.json b/rust/sealevel/environments/mainnet3/warp-routes/TIA-stride-eclipse/program-ids.json new file mode 100644 index 0000000000..6f10e10143 --- /dev/null +++ b/rust/sealevel/environments/mainnet3/warp-routes/TIA-stride-eclipse/program-ids.json @@ -0,0 +1,10 @@ +{ + "stride": { + "hex": "0x0b1798722ccf813f617ffa8d52d36302544807497434740d98e693e9abb245d1", + "base58": "kJMTJuh4tzhzfTV261wh2cisqb7NNev6ZE6czwB3ss6" + }, + "eclipsemainnet": { + "hex": "0xa0c167513f4d025217a48891973c3dbe41e10e76230033ef5d676299a18ca7f5", + "base58": "BpXHAiktwjx7fN6M9ST9wr6qKAsH27wZFhdHEhReJsR6" + } +} \ No newline at end of file diff --git a/rust/sealevel/environments/mainnet3/warp-routes/TIA-stride-eclipse/token-config.json b/rust/sealevel/environments/mainnet3/warp-routes/TIA-stride-eclipse/token-config.json new file mode 100644 index 0000000000..f501f4ee81 --- /dev/null +++ b/rust/sealevel/environments/mainnet3/warp-routes/TIA-stride-eclipse/token-config.json @@ -0,0 +1,17 @@ +{ + "eclipsemainnet": { + "type": "synthetic", + "decimals": 6, + "remoteDecimals": 6, + "name": "Celestia", + "symbol": "TIA", + "uri": "https://raw.githubusercontent.com/hyperlane-xyz/hyperlane-registry/b21b78514a42e8c050b36472c8325fd4b5177366/deployments/warp_routes/TIA/metadata.json", + "interchainGasPaymaster": "3Wp4qKkgf4tjXz1soGyTSndCgBPLZFSrZkiDZ8Qp9EEj" + }, + "stride": { + "type": "collateral", + "decimals": 6, + "token": "ibc/BF3B4F53F3694B66E13C23107C84B6485BD2B96296BB7EC680EA77BBA75B4801", + "foreignDeployment": "0x0b1798722ccf813f617ffa8d52d36302544807497434740d98e693e9abb245d1" + } +} diff --git a/rust/sealevel/environments/mainnet3/warp-routes/stTIA-stride-eclipse/program-ids.json b/rust/sealevel/environments/mainnet3/warp-routes/stTIA-stride-eclipse/program-ids.json new file mode 100644 index 0000000000..1af852d361 --- /dev/null +++ b/rust/sealevel/environments/mainnet3/warp-routes/stTIA-stride-eclipse/program-ids.json @@ -0,0 +1,10 @@ +{ + "stride": { + "hex": "0x8d7a6737fdd9545dc77d1c5cc2d26cb1321f6c67f0facb030149da9cc58f0cbe", + "base58": "AXGjtKVpzYdXYX155z6qYQC4Up7fi5LPKNXAK32gi3x9" + }, + "eclipsemainnet": { + "hex": "0x0d258188d0761163da174da890d0c1becdee51a01dbc9e2a6bfcb342140eb509", + "base58": "tKUHyJ5NxhnwU94JUmzh1ekukDcHHX8mZF6fqxbMwX6" + } +} \ No newline at end of file diff --git a/rust/sealevel/environments/mainnet3/warp-routes/stTIA-stride-eclipse/token-config.json b/rust/sealevel/environments/mainnet3/warp-routes/stTIA-stride-eclipse/token-config.json new file mode 100644 index 0000000000..c59c12bb6c --- /dev/null +++ b/rust/sealevel/environments/mainnet3/warp-routes/stTIA-stride-eclipse/token-config.json @@ -0,0 +1,17 @@ +{ + "eclipsemainnet": { + "type": "synthetic", + "decimals": 6, + "remoteDecimals": 6, + "name": "Stride Staked TIA", + "symbol": "stTIA", + "uri": "https://raw.githubusercontent.com/hyperlane-xyz/hyperlane-registry/dee58183e51f4eb43e84dbac0e595a4b389dbe80/deployments/warp_routes/stTIA/metadata.json", + "interchainGasPaymaster": "3Wp4qKkgf4tjXz1soGyTSndCgBPLZFSrZkiDZ8Qp9EEj" + }, + "stride": { + "type": "collateral", + "decimals": 6, + "token": "stutia", + "foreignDeployment": "0x8d7a6737fdd9545dc77d1c5cc2d26cb1321f6c67f0facb030149da9cc58f0cbe" + } +} diff --git a/typescript/infra/config/environments/mainnet3/warp/STTIA-eclipsestride-deployments.yaml b/typescript/infra/config/environments/mainnet3/warp/STTIA-eclipsestride-deployments.yaml new file mode 100644 index 0000000000..18e934fffb --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/warp/STTIA-eclipsestride-deployments.yaml @@ -0,0 +1,24 @@ +# Configs and artifacts for the deployment of Hyperlane Warp Routes +description: Hyperlane Warp Route artifacts +timestamp: '2024-10-18T14:00:00.000Z' +deployer: Abacus Works (Hyperlane) +data: + config: + stride: + protocolType: cosmos + type: collateral + hypAddress: stride134axwdlam929m3mar3wv95nvkyep7mr87ravkqcpf8dfe3v0pjlqwrw6ee + # dummy tokenAddress since stTIA is native + tokenAddress: 'stride134axwdlam929m3mar3wv95nvkyep7mr87ravkqcpf8dfe3v0pjlqwrw6ee' + name: Stride Staked TIA + symbol: stTIA + decimals: 6 + eclipsemainnet: + protocolType: sealevel + type: synthetic + hypAddress: 'tKUHyJ5NxhnwU94JUmzh1ekukDcHHX8mZF6fqxbMwX6' + tokenAddress: 'V5m1Cc9VK61mKL8xVYrjR7bjD2BC5VpADLa6ws3G8KM' + isSpl2022: true + name: Turbo Eth + symbol: tETH + decimals: 6 diff --git a/typescript/infra/config/environments/mainnet3/warp/TIA-eclipsestride-deployments.yaml b/typescript/infra/config/environments/mainnet3/warp/TIA-eclipsestride-deployments.yaml new file mode 100644 index 0000000000..999e8ea951 --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/warp/TIA-eclipsestride-deployments.yaml @@ -0,0 +1,23 @@ +# Configs and artifacts for the deployment of Hyperlane Warp Routes +description: Hyperlane Warp Route artifacts +timestamp: '2024-10-18T14:00:00.000Z' +deployer: Abacus Works (Hyperlane) +data: + config: + stride: + protocolType: cosmos + type: collateral + hypAddress: stride1pvtesu3ve7qn7ctll2x495mrqf2ysp6fws68grvcu6f7n2ajghgsh2jdj6 + tokenAddress: ibc/BF3B4F53F3694B66E13C23107C84B6485BD2B96296BB7EC680EA77BBA75B4801 + name: Celestia + symbol: TIA + decimals: 6 + eclipsemainnet: + protocolType: sealevel + type: synthetic + hypAddress: 'BpXHAiktwjx7fN6M9ST9wr6qKAsH27wZFhdHEhReJsR6' + tokenAddress: '9RryNMhAVJpAwAGjCAMKbbTFwgjapqPkzpGMfTQhEjf8' + isSpl2022: true + name: Turbo Eth + symbol: tETH + decimals: 6 diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getEclipseStrideSTTIAWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getEclipseStrideSTTIAWarpConfig.ts new file mode 100644 index 0000000000..eaf935f9bb --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/warp/configGetters/getEclipseStrideSTTIAWarpConfig.ts @@ -0,0 +1,29 @@ +import { + ChainMap, + RouterConfig, + TokenRouterConfig, + TokenType, +} from '@hyperlane-xyz/sdk'; + +export const getEclipseStrideTiaWarpConfig = async ( + _routerConfig: ChainMap, +): Promise> => { + // @ts-ignore - foreignDeployment configs don't conform to the TokenRouterConfig + const eclipsemainnet: TokenRouterConfig = { + type: TokenType.synthetic, + foreignDeployment: 'BpXHAiktwjx7fN6M9ST9wr6qKAsH27wZFhdHEhReJsR6', + gas: 300_000, + }; + + // @ts-ignore - foreignDeployment configs don't conform to the TokenRouterConfig + const stride: TokenRouterConfig = { + type: TokenType.collateral, + foreignDeployment: + 'stride1pvtesu3ve7qn7ctll2x495mrqf2ysp6fws68grvcu6f7n2ajghgsh2jdj6', + }; + + return { + eclipsemainnet, + stride, + }; +}; diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getEclipseStrideTIAWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getEclipseStrideTIAWarpConfig.ts new file mode 100644 index 0000000000..aa2cf6a103 --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/warp/configGetters/getEclipseStrideTIAWarpConfig.ts @@ -0,0 +1,29 @@ +import { + ChainMap, + RouterConfig, + TokenRouterConfig, + TokenType, +} from '@hyperlane-xyz/sdk'; + +export const getEclipseStrideStTiaWarpConfig = async ( + _routerConfig: ChainMap, +): Promise> => { + // @ts-ignore - foreignDeployment configs don't conform to the TokenRouterConfig + const eclipsemainnet: TokenRouterConfig = { + type: TokenType.synthetic, + foreignDeployment: 'tKUHyJ5NxhnwU94JUmzh1ekukDcHHX8mZF6fqxbMwX6', + gas: 300_000, + }; + + // @ts-ignore - foreignDeployment configs don't conform to the TokenRouterConfig + const stride: TokenRouterConfig = { + type: TokenType.collateral, + foreignDeployment: + 'stride134axwdlam929m3mar3wv95nvkyep7mr87ravkqcpf8dfe3v0pjlqwrw6ee', + }; + + return { + eclipsemainnet, + stride, + }; +}; diff --git a/typescript/infra/config/environments/mainnet3/warp/warpIds.ts b/typescript/infra/config/environments/mainnet3/warp/warpIds.ts index debc81326c..a90601c356 100644 --- a/typescript/infra/config/environments/mainnet3/warp/warpIds.ts +++ b/typescript/infra/config/environments/mainnet3/warp/warpIds.ts @@ -6,6 +6,8 @@ export enum WarpRouteIds { ArbitrumNeutronTIA = 'TIA/arbitrum-neutron', EclipseSolanaSOL = 'SOL/eclipsemainnet-solanamainnet', EclipseSolanaWIF = 'WIF/eclipsemainnet-solanamainnet', + EclipseStrideSTTIA = 'stTIA/eclipse-stride', + EclipseStrideTIA = 'TIA/eclipse-stride', EthereumInevmUSDC = 'USDC/ethereum-inevm', EthereumInevmUSDT = 'USDT/ethereum-inevm', EthereumEclipseTETH = 'tETH/eclipsemainnet-ethereum', diff --git a/typescript/infra/config/warp.ts b/typescript/infra/config/warp.ts index 2b7c44ee40..84086342e7 100644 --- a/typescript/infra/config/warp.ts +++ b/typescript/infra/config/warp.ts @@ -12,6 +12,8 @@ import { getAncient8EthereumUSDCWarpConfig } from './environments/mainnet3/warp/ import { getArbitrumEthereumZircuitAmphrETHWarpConfig } from './environments/mainnet3/warp/configGetters/getArbitrumEthereumZircuitAmphrETHWarpConfig.js'; import { getArbitrumNeutronEclipWarpConfig } from './environments/mainnet3/warp/configGetters/getArbitrumNeutronEclipWarpConfig.js'; import { getArbitrumNeutronTiaWarpConfig } from './environments/mainnet3/warp/configGetters/getArbitrumNeutronTiaWarpConfig.js'; +import { getEclipseStrideTiaWarpConfig } from './environments/mainnet3/warp/configGetters/getEclipseStrideSTTIAWarpConfig.js'; +import { getEclipseStrideStTiaWarpConfig } from './environments/mainnet3/warp/configGetters/getEclipseStrideTIAWarpConfig.js'; import { getEthereumInevmUSDCWarpConfig } from './environments/mainnet3/warp/configGetters/getEthereumInevmUSDCWarpConfig.js'; import { getEthereumInevmUSDTWarpConfig } from './environments/mainnet3/warp/configGetters/getEthereumInevmUSDTWarpConfig.js'; import { getEthereumSeiFastUSDWarpConfig } from './environments/mainnet3/warp/configGetters/getEthereumSeiFastUSDWarpConfig.js'; @@ -50,6 +52,8 @@ export const warpConfigGetterMap: Record< [WarpRouteIds.EthereumVictionUSDT]: getEthereumVictionUSDTWarpConfig, [WarpRouteIds.EthereumZircuitPZETH]: getRenzoPZETHWarpConfig, [WarpRouteIds.MantapacificNeutronTIA]: getMantapacificNeutronTiaWarpConfig, + [WarpRouteIds.EclipseStrideTIA]: getEclipseStrideTiaWarpConfig, + [WarpRouteIds.EclipseStrideSTTIA]: getEclipseStrideStTiaWarpConfig, }; export async function getWarpConfig( diff --git a/typescript/infra/src/warp/helm.ts b/typescript/infra/src/warp/helm.ts index f5aa0e9811..e7296ad0c6 100644 --- a/typescript/infra/src/warp/helm.ts +++ b/typescript/infra/src/warp/helm.ts @@ -27,7 +27,7 @@ export class WarpRouteMonitorHelmManager extends HelmManager { return { image: { repository: 'gcr.io/abacus-labs-dev/hyperlane-monorepo', - tag: '6945b20-20241022-155935', + tag: 'ee5ec6b-20241025-133236', }, configFilePath: pathRelativeToMonorepoRoot, fullnameOverride: this.helmReleaseName, From d5bdb2c28a9332c8cc54faddd39147cb9a0f824e Mon Sep 17 00:00:00 2001 From: Mohammed Hussan Date: Fri, 25 Oct 2024 15:31:17 +0100 Subject: [PATCH 23/61] feat(warpMonitor): Support collateral value monitoring (#4545) ### Description - Support continuous value monitoring by allowing promethues to scrape value metrics for native and collateral tokens - Uses the `CoinGeckoTokenPriceGetter` - Added `getTokenPriceByIds` helper method to the `CoinGeckoTokenPriceGetter`, to support fetching prices using the coin gecko id only - Add a warp_route_id label to metrics ### Testing Manual --- .../mainnet3/warp/EZETH-deployments.yaml | 1 + .../warp/ancient8-USDC-deployments.yaml | 1 + .../warp/bsc-lumia-LUMIA-deployments.yaml | 1 + .../warp/eclipse-SOL-deployments.yaml | 1 + .../warp/eclipse-USDC-deployments.yaml | 2 + .../warp/eclipse-WIF-deployments.yaml | 1 + .../warp/ethereumUSDC-inevm-deployments.yaml | 1 + .../warp/ethereumUSDT-inevm-deployments.yaml | 1 + .../warp/injective-inevm-deployments.yaml | 1 + .../neutron-mantapacific-deployments.yaml | 1 + .../warp/sei-FASTUSD-deployments.yaml | 1 + .../viction-ethereum-ETH-deployments.yaml | 1 + .../viction-ethereum-USDC-deployments.yaml | 1 + .../viction-ethereum-USDT-deployments.yaml | 1 + typescript/infra/package.json | 2 +- .../monitor-warp-routes-balances.ts | 283 ++++++++++++++---- typescript/sdk/src/gas/token-prices.ts | 93 +++--- .../sdk/src/metadata/warpRouteConfig.ts | 1 + yarn.lock | 12 +- 19 files changed, 316 insertions(+), 90 deletions(-) diff --git a/typescript/infra/config/environments/mainnet3/warp/EZETH-deployments.yaml b/typescript/infra/config/environments/mainnet3/warp/EZETH-deployments.yaml index b836636457..26a0a5807d 100644 --- a/typescript/infra/config/environments/mainnet3/warp/EZETH-deployments.yaml +++ b/typescript/infra/config/environments/mainnet3/warp/EZETH-deployments.yaml @@ -10,6 +10,7 @@ data: symbol: ezETH hypAddress: '0xC59336D8edDa9722B4f1Ec104007191Ec16f7087' tokenAddress: '0xbf5495Efe5DB9ce00f80364C8B423567e58d2110' + tokenCoinGeckoId: renzo-restaked-eth decimals: 18 bsc: protocolType: ethereum diff --git a/typescript/infra/config/environments/mainnet3/warp/ancient8-USDC-deployments.yaml b/typescript/infra/config/environments/mainnet3/warp/ancient8-USDC-deployments.yaml index 07be060273..d87b3fa0a7 100644 --- a/typescript/infra/config/environments/mainnet3/warp/ancient8-USDC-deployments.yaml +++ b/typescript/infra/config/environments/mainnet3/warp/ancient8-USDC-deployments.yaml @@ -10,6 +10,7 @@ data: type: collateral hypAddress: '0x8b4192B9Ad1fCa440A5808641261e5289e6de95D' tokenAddress: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' # USDC + tokenCoinGeckoId: usd-coin name: USDC symbol: USDC decimals: 6 diff --git a/typescript/infra/config/environments/mainnet3/warp/bsc-lumia-LUMIA-deployments.yaml b/typescript/infra/config/environments/mainnet3/warp/bsc-lumia-LUMIA-deployments.yaml index 9db075248d..d106ac884e 100644 --- a/typescript/infra/config/environments/mainnet3/warp/bsc-lumia-LUMIA-deployments.yaml +++ b/typescript/infra/config/environments/mainnet3/warp/bsc-lumia-LUMIA-deployments.yaml @@ -10,6 +10,7 @@ data: type: collateral hypAddress: '0xdD313D475f8A9d81CBE2eA953a357f52e10BA357' tokenAddress: '0xd9343a049d5dbd89cd19dc6bca8c48fb3a0a42a7' + tokenCoinGeckoId: lumia name: Lumia Token symbol: LUMIA decimals: 18 diff --git a/typescript/infra/config/environments/mainnet3/warp/eclipse-SOL-deployments.yaml b/typescript/infra/config/environments/mainnet3/warp/eclipse-SOL-deployments.yaml index 0469f45ebc..711e6d4282 100644 --- a/typescript/infra/config/environments/mainnet3/warp/eclipse-SOL-deployments.yaml +++ b/typescript/infra/config/environments/mainnet3/warp/eclipse-SOL-deployments.yaml @@ -9,6 +9,7 @@ data: protocolType: sealevel type: native hypAddress: '8DtAGQpcMuD5sG3KdxDy49ydqXUggR1LQtebh2TECbAc' + tokenCoinGeckoId: solana name: Solana symbol: SOL decimals: 9 diff --git a/typescript/infra/config/environments/mainnet3/warp/eclipse-USDC-deployments.yaml b/typescript/infra/config/environments/mainnet3/warp/eclipse-USDC-deployments.yaml index 2955d2c84b..7405cb94d2 100644 --- a/typescript/infra/config/environments/mainnet3/warp/eclipse-USDC-deployments.yaml +++ b/typescript/infra/config/environments/mainnet3/warp/eclipse-USDC-deployments.yaml @@ -10,6 +10,7 @@ data: type: collateral hypAddress: '0xe1De9910fe71cC216490AC7FCF019e13a34481D7' tokenAddress: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' # USDC + tokenCoinGeckoId: usd-coin name: USDC symbol: USDC decimals: 6 @@ -18,6 +19,7 @@ data: type: collateral hypAddress: '3EpVCPUgyjq2MfGeCttyey6bs5zya5wjYZ2BE6yDg6bm' tokenAddress: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v' + tokenCoinGeckoId: usd-coin isSpl2022: false name: USDC symbol: USDC diff --git a/typescript/infra/config/environments/mainnet3/warp/eclipse-WIF-deployments.yaml b/typescript/infra/config/environments/mainnet3/warp/eclipse-WIF-deployments.yaml index 94e44a8b1a..1b7245c91c 100644 --- a/typescript/infra/config/environments/mainnet3/warp/eclipse-WIF-deployments.yaml +++ b/typescript/infra/config/environments/mainnet3/warp/eclipse-WIF-deployments.yaml @@ -10,6 +10,7 @@ data: type: collateral hypAddress: 'CuQmsT4eSF4dYiiGUGYYQxJ7c58pUAD5ADE3BbFGzQKx' tokenAddress: 'EKpQGSJtjMFqKZ9KQanSqYXRcF8fBopzLHYxdM65zcjm' + tokenCoinGeckoId: dogwifcoin name: dogwifhat symbol: WIF decimals: 9 diff --git a/typescript/infra/config/environments/mainnet3/warp/ethereumUSDC-inevm-deployments.yaml b/typescript/infra/config/environments/mainnet3/warp/ethereumUSDC-inevm-deployments.yaml index 0d8ff77557..90f22e0dff 100644 --- a/typescript/infra/config/environments/mainnet3/warp/ethereumUSDC-inevm-deployments.yaml +++ b/typescript/infra/config/environments/mainnet3/warp/ethereumUSDC-inevm-deployments.yaml @@ -10,6 +10,7 @@ data: type: collateral hypAddress: '0xED56728fb977b0bBdacf65bCdD5e17Bb7e84504f' tokenAddress: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' # USDC + tokenCoinGeckoId: usd-coin name: USDC symbol: USDC decimals: 6 diff --git a/typescript/infra/config/environments/mainnet3/warp/ethereumUSDT-inevm-deployments.yaml b/typescript/infra/config/environments/mainnet3/warp/ethereumUSDT-inevm-deployments.yaml index 25ec599f73..91976ddd98 100644 --- a/typescript/infra/config/environments/mainnet3/warp/ethereumUSDT-inevm-deployments.yaml +++ b/typescript/infra/config/environments/mainnet3/warp/ethereumUSDT-inevm-deployments.yaml @@ -10,6 +10,7 @@ data: type: collateral hypAddress: '0xab852e67bf03E74C89aF67C4BA97dd1088D3dA19' tokenAddress: '0xdac17f958d2ee523a2206206994597c13d831ec7' # USDT + tokenCoinGeckoId: tether name: Tether USD symbol: USDT decimals: 6 diff --git a/typescript/infra/config/environments/mainnet3/warp/injective-inevm-deployments.yaml b/typescript/infra/config/environments/mainnet3/warp/injective-inevm-deployments.yaml index efabaf59da..cd39382ea3 100644 --- a/typescript/infra/config/environments/mainnet3/warp/injective-inevm-deployments.yaml +++ b/typescript/infra/config/environments/mainnet3/warp/injective-inevm-deployments.yaml @@ -9,6 +9,7 @@ data: protocolType: cosmos type: native hypAddress: inj1mv9tjvkaw7x8w8y9vds8pkfq46g2vcfkjehc6k + tokenCoinGeckoId: injective-protocol name: Injective Coin symbol: INJ decimals: 18 diff --git a/typescript/infra/config/environments/mainnet3/warp/neutron-mantapacific-deployments.yaml b/typescript/infra/config/environments/mainnet3/warp/neutron-mantapacific-deployments.yaml index ae13acf3d7..bf4e1770ae 100644 --- a/typescript/infra/config/environments/mainnet3/warp/neutron-mantapacific-deployments.yaml +++ b/typescript/infra/config/environments/mainnet3/warp/neutron-mantapacific-deployments.yaml @@ -10,6 +10,7 @@ data: type: collateral hypAddress: neutron1ch7x3xgpnj62weyes8vfada35zff6z59kt2psqhnx9gjnt2ttqdqtva3pa tokenAddress: ibc/773B4D0A3CD667B2275D5A4A7A2F0909C0BA0F4059C0B9181E680DDF4965DCC7 + tokenCoinGeckoId: celestia name: Celestia symbol: TIA decimals: 6 diff --git a/typescript/infra/config/environments/mainnet3/warp/sei-FASTUSD-deployments.yaml b/typescript/infra/config/environments/mainnet3/warp/sei-FASTUSD-deployments.yaml index 885c1a4dbd..ed9672e416 100644 --- a/typescript/infra/config/environments/mainnet3/warp/sei-FASTUSD-deployments.yaml +++ b/typescript/infra/config/environments/mainnet3/warp/sei-FASTUSD-deployments.yaml @@ -8,6 +8,7 @@ data: type: collateral hypAddress: '0x9AD81058c6C3Bf552C9014CB30E824717A0ee21b' tokenAddress: '0x15700B564Ca08D9439C58cA5053166E8317aa138' + tokenCoinGeckoId: elixir-deusd # unique setup where we want deUSD to be deposited as collateral and we want fastUSD to be minted as a synthetic on sei name: fastUSD symbol: fastUSD decimals: 18 diff --git a/typescript/infra/config/environments/mainnet3/warp/viction-ethereum-ETH-deployments.yaml b/typescript/infra/config/environments/mainnet3/warp/viction-ethereum-ETH-deployments.yaml index 8923facc00..c3e03b54d5 100644 --- a/typescript/infra/config/environments/mainnet3/warp/viction-ethereum-ETH-deployments.yaml +++ b/typescript/infra/config/environments/mainnet3/warp/viction-ethereum-ETH-deployments.yaml @@ -9,6 +9,7 @@ data: protocolType: ethereum type: native hypAddress: '0x15b5D6B614242B118AA404528A7f3E2Ad241e4A4' + tokenCoinGeckoId: ethereum name: Ether symbol: ETH decimals: 18 diff --git a/typescript/infra/config/environments/mainnet3/warp/viction-ethereum-USDC-deployments.yaml b/typescript/infra/config/environments/mainnet3/warp/viction-ethereum-USDC-deployments.yaml index a278a93e8c..8a84e6a7c5 100644 --- a/typescript/infra/config/environments/mainnet3/warp/viction-ethereum-USDC-deployments.yaml +++ b/typescript/infra/config/environments/mainnet3/warp/viction-ethereum-USDC-deployments.yaml @@ -10,6 +10,7 @@ data: type: collateral hypAddress: '0x31Dca7762930f56D81292f85E65c9D67575804fE' tokenAddress: '0x31Dca7762930f56D81292f85E65c9D67575804fE' # USDC + tokenCoinGeckoId: usd-coin name: USD Coin symbol: USDC decimals: 6 diff --git a/typescript/infra/config/environments/mainnet3/warp/viction-ethereum-USDT-deployments.yaml b/typescript/infra/config/environments/mainnet3/warp/viction-ethereum-USDT-deployments.yaml index 8ddaa1e085..a225980420 100644 --- a/typescript/infra/config/environments/mainnet3/warp/viction-ethereum-USDT-deployments.yaml +++ b/typescript/infra/config/environments/mainnet3/warp/viction-ethereum-USDT-deployments.yaml @@ -10,6 +10,7 @@ data: type: collateral hypAddress: '0x4221a16A01F61c2b38A03C52d828a7041f6AAA49' tokenAddress: '0xdAC17F958D2ee523a2206206994597C13D831ec7' # USDT + tokenCoinGeckoId: tether name: Tether USD symbol: USDT decimals: 6 diff --git a/typescript/infra/package.json b/typescript/infra/package.json index 375d06c59d..81361ac85f 100644 --- a/typescript/infra/package.json +++ b/typescript/infra/package.json @@ -14,7 +14,7 @@ "@ethersproject/providers": "^5.7.2", "@google-cloud/secret-manager": "^5.5.0", "@hyperlane-xyz/helloworld": "5.6.1", - "@hyperlane-xyz/registry": "4.7.0", + "@hyperlane-xyz/registry": "4.10.0", "@hyperlane-xyz/sdk": "5.6.1", "@hyperlane-xyz/utils": "5.6.1", "@inquirer/prompts": "^5.3.8", diff --git a/typescript/infra/scripts/warp-routes/monitor-warp-routes-balances.ts b/typescript/infra/scripts/warp-routes/monitor-warp-routes-balances.ts index 3d946a9e68..2d588a89c2 100644 --- a/typescript/infra/scripts/warp-routes/monitor-warp-routes-balances.ts +++ b/typescript/infra/scripts/warp-routes/monitor-warp-routes-balances.ts @@ -9,10 +9,12 @@ import { IXERC20__factory, } from '@hyperlane-xyz/core'; import { ERC20__factory } from '@hyperlane-xyz/core'; +import { createWarpRouteConfigId } from '@hyperlane-xyz/registry'; import { ChainMap, ChainMetadata, ChainName, + CoinGeckoTokenPriceGetter, CosmNativeTokenAdapter, CwNativeTokenAdapter, MultiProtocolProvider, @@ -38,17 +40,41 @@ import { getEnvironmentConfig } from '../core-utils.js'; const logger = rootLogger.child({ module: 'warp-balance-monitor' }); const metricsRegister = new Registry(); + +interface WarpRouteMetrics { + chain_name: ChainName; + token_address: string; + token_name: string; + wallet_address: string; + token_type: TokenType; + warp_route_id: string; + related_chain_names: string; +} + +type WarpRouteMetricLabels = keyof WarpRouteMetrics; + +const warpRouteMetricLabels: WarpRouteMetricLabels[] = [ + 'chain_name', + 'token_address', + 'token_name', + 'wallet_address', + 'token_type', + 'warp_route_id', + 'related_chain_names', +]; + const warpRouteTokenBalance = new Gauge({ name: 'hyperlane_warp_route_token_balance', help: 'HypERC20 token balance of a Warp Route', registers: [metricsRegister], - labelNames: [ - 'chain_name', - 'token_address', - 'token_name', - 'wallet_address', - 'token_type', - ], + labelNames: warpRouteMetricLabels, +}); + +const warpRouteCollateralValue = new Gauge({ + name: 'hyperlane_warp_route_collateral_value', + help: 'Total value of collateral held in a HypERC20Collateral or HypNative contract of a Warp Route', + registers: [metricsRegister], + labelNames: warpRouteMetricLabels, }); const xERC20LimitsGauge = new Gauge({ @@ -66,6 +92,11 @@ interface xERC20Limit { burnMax: number; } +interface WarpRouteInfo { + balance: number; + valueUSD?: number; +} + export function readWarpRouteConfig(filePath: string) { const config = readYaml(filePath); if (!config) throw new Error(`No warp config found at ${filePath}`); @@ -112,7 +143,8 @@ async function main(): Promise { async function checkBalance( tokenConfig: WarpRouteConfig, multiProtocolProvider: MultiProtocolProvider, -): Promise> { + tokenPriceGetter: CoinGeckoTokenPriceGetter, +): Promise> { const output = objMap( tokenConfig, async (chain: ChainName, token: WarpRouteConfig[ChainName]) => { @@ -122,8 +154,12 @@ async function checkBalance( case ProtocolType.Ethereum: { const provider = multiProtocolProvider.getEthersV5Provider(chain); const nativeBalance = await provider.getBalance(token.hypAddress); - return parseFloat( - ethers.utils.formatUnits(nativeBalance, token.decimals), + + return getNativeTokenWarpInfo( + nativeBalance, + token.decimals, + tokenPriceGetter, + chain, ); } case ProtocolType.Sealevel: { @@ -142,8 +178,12 @@ async function checkBalance( const balance = ethers.BigNumber.from( await adapter.getBalance(token.hypAddress), ); - return parseFloat( - ethers.utils.formatUnits(balance, token.decimals), + + return getNativeTokenWarpInfo( + balance, + token.decimals, + tokenPriceGetter, + chain, ); } case ProtocolType.Cosmos: { @@ -156,8 +196,12 @@ async function checkBalance( { ibcDenom: token.ibcDenom }, ); const tokenBalance = await adapter.getBalance(token.hypAddress); - return parseFloat( - ethers.utils.formatUnits(tokenBalance, token.decimals), + + return getNativeTokenWarpInfo( + tokenBalance, + token.decimals, + tokenPriceGetter, + chain, ); } } @@ -177,8 +221,11 @@ async function checkBalance( token.hypAddress, ); - return parseFloat( - ethers.utils.formatUnits(collateralBalance, token.decimals), + return getCollateralTokenWarpInfo( + collateralBalance, + token.decimals, + tokenPriceGetter, + token.tokenCoinGeckoId, ); } case ProtocolType.Sealevel: { @@ -198,8 +245,12 @@ async function checkBalance( const collateralBalance = ethers.BigNumber.from( await adapter.getBalance(token.hypAddress), ); - return parseFloat( - ethers.utils.formatUnits(collateralBalance, token.decimals), + + return getCollateralTokenWarpInfo( + collateralBalance, + token.decimals, + tokenPriceGetter, + token.tokenCoinGeckoId, ); } case ProtocolType.Cosmos: { @@ -216,8 +267,12 @@ async function checkBalance( const collateralBalance = ethers.BigNumber.from( await adapter.getBalance(token.hypAddress), ); - return parseFloat( - ethers.utils.formatUnits(collateralBalance, token.decimals), + + return getCollateralTokenWarpInfo( + collateralBalance, + token.decimals, + tokenPriceGetter, + token.tokenCoinGeckoId, ); } } @@ -232,9 +287,11 @@ async function checkBalance( provider, ); const syntheticBalance = await tokenContract.totalSupply(); - return parseFloat( - ethers.utils.formatUnits(syntheticBalance, token.decimals), - ); + return { + balance: parseFloat( + ethers.utils.formatUnits(syntheticBalance, token.decimals), + ), + }; } case ProtocolType.Sealevel: { if (!token.tokenAddress) @@ -253,13 +310,15 @@ async function checkBalance( const syntheticBalance = ethers.BigNumber.from( await adapter.getTotalSupply(), ); - return parseFloat( - ethers.utils.formatUnits(syntheticBalance, token.decimals), - ); + return { + balance: parseFloat( + ethers.utils.formatUnits(syntheticBalance, token.decimals), + ), + }; } case ProtocolType.Cosmos: // TODO - cosmos synthetic - return 0; + return { balance: 0 }; } break; } @@ -275,9 +334,11 @@ async function checkBalance( const xerc20 = IXERC20__factory.connect(xerc20Address, provider); const syntheticBalance = await xerc20.totalSupply(); - return parseFloat( - ethers.utils.formatUnits(syntheticBalance, token.decimals), - ); + return { + balance: parseFloat( + ethers.utils.formatUnits(syntheticBalance, token.decimals), + ), + }; } default: throw new Error( @@ -307,8 +368,11 @@ async function checkBalance( xerc20LockboxAddress, ); - return parseFloat( - ethers.utils.formatUnits(collateralBalance, token.decimals), + return getCollateralTokenWarpInfo( + collateralBalance, + token.decimals, + tokenPriceGetter, + token.tokenCoinGeckoId, ); } default: @@ -318,7 +382,7 @@ async function checkBalance( } } } - return 0; + return { balance: 0 }; }, ); @@ -327,22 +391,44 @@ async function checkBalance( export function updateTokenBalanceMetrics( tokenConfig: WarpRouteConfig, - balances: ChainMap, + balances: ChainMap, ) { objMap(tokenConfig, (chain: ChainName, token: WarpRouteConfig[ChainName]) => { - warpRouteTokenBalance - .labels({ - chain_name: chain, - token_address: token.tokenAddress ?? ethers.constants.AddressZero, - token_name: token.name, - wallet_address: token.hypAddress, - token_type: token.type, - }) - .set(balances[chain]); + const metrics: WarpRouteMetrics = { + chain_name: chain, + token_address: token.tokenAddress ?? ethers.constants.AddressZero, + token_name: token.name, + wallet_address: token.hypAddress, + token_type: token.type, + warp_route_id: createWarpRouteConfigId( + token.symbol, + Object.keys(tokenConfig) as ChainName[], + ), + related_chain_names: Object.keys(tokenConfig) + .filter((chainName) => chainName !== chain) + .sort() + .join(','), + }; + + warpRouteTokenBalance.labels(metrics).set(balances[chain].balance); + if (balances[chain].valueUSD) { + warpRouteCollateralValue + .labels(metrics) + .set(balances[chain].valueUSD as number); + logger.debug('Collateral value updated for chain', { + chain, + related_chain_names: metrics.related_chain_names, + warp_route_id: metrics.warp_route_id, + token: metrics.token_name, + value: balances[chain].valueUSD, + }); + } logger.debug('Wallet balance updated for chain', { chain, - token: token.name, - balance: balances[chain], + related_chain_names: metrics.related_chain_names, + warp_route_id: metrics.warp_route_id, + token: metrics.token_name, + balance: balances[chain].balance, }); }); } @@ -468,27 +554,120 @@ const getXERC20Limit = async ( }; }; +async function getTokenPriceByChain( + chain: ChainName, + tokenPriceGetter: CoinGeckoTokenPriceGetter, +): Promise { + try { + return await tokenPriceGetter.getTokenPrice(chain); + } catch (e) { + logger.warn('Error getting token price', e); + return undefined; + } +} + +async function getNativeTokenValue( + chain: ChainName, + balanceFloat: number, + tokenPriceGetter: CoinGeckoTokenPriceGetter, +): Promise { + const price = await getTokenPriceByChain(chain, tokenPriceGetter); + logger.debug(`${chain} native token price ${price}`); + if (!price) return undefined; + return balanceFloat * price; +} + +async function getNativeTokenWarpInfo( + balance: ethers.BigNumber | bigint, + decimal: number, + tokenPriceGetter: CoinGeckoTokenPriceGetter, + chain: ChainName, +): Promise { + const balanceFloat = parseFloat(ethers.utils.formatUnits(balance, decimal)); + const value = await getNativeTokenValue( + chain, + balanceFloat, + tokenPriceGetter, + ); + return { balance: balanceFloat, valueUSD: value }; +} + +async function getCollateralTokenPrice( + tokenCoinGeckoId: string | undefined, + tokenPriceGetter: CoinGeckoTokenPriceGetter, +): Promise { + if (!tokenCoinGeckoId) return undefined; + const prices = await tokenPriceGetter.getTokenPriceByIds([tokenCoinGeckoId]); + if (!prices) return undefined; + return prices[0]; +} + +async function getCollateralTokenValue( + tokenCoinGeckoId: string | undefined, + balanceFloat: number, + tokenPriceGetter: CoinGeckoTokenPriceGetter, +): Promise { + const price = await getCollateralTokenPrice( + tokenCoinGeckoId, + tokenPriceGetter, + ); + logger.debug(`${tokenCoinGeckoId} token price ${price}`); + if (!price) return undefined; + return balanceFloat * price; +} + +async function getCollateralTokenWarpInfo( + balance: ethers.BigNumber | bigint, + decimal: number, + tokenPriceGetter: CoinGeckoTokenPriceGetter, + tokenCoinGeckoId?: string, +): Promise { + const balanceFloat = parseFloat(ethers.utils.formatUnits(balance, decimal)); + const value = await getCollateralTokenValue( + tokenCoinGeckoId, + balanceFloat, + tokenPriceGetter, + ); + return { balance: balanceFloat, valueUSD: value }; +} + async function checkWarpRouteMetrics( checkFrequency: number, tokenConfig: WarpRouteConfig, chainMetadata: ChainMap, ) { + const tokenPriceGetter = + CoinGeckoTokenPriceGetter.withDefaultCoinGecko(chainMetadata); + setInterval(async () => { try { const multiProtocolProvider = new MultiProtocolProvider(chainMetadata); - const balances = await checkBalance(tokenConfig, multiProtocolProvider); + const balances = await checkBalance( + tokenConfig, + multiProtocolProvider, + tokenPriceGetter, + ); logger.info('Token Balances:', balances); updateTokenBalanceMetrics(tokenConfig, balances); } catch (e) { logger.error('Error checking balances', e); } - try { - const xERC20Limits = await getXERC20Limits(tokenConfig, chainMetadata); - logger.info('xERC20 Limits:', xERC20Limits); - updateXERC20LimitsMetrics(xERC20Limits); - } catch (e) { - logger.error('Error checking xERC20 limits', e); + // only check xERC20 limits if there are xERC20 tokens in the config + if ( + Object.keys(tokenConfig).some( + (chain) => + tokenConfig[chain].type === TokenType.XERC20 || + tokenConfig[chain].type === TokenType.XERC20Lockbox, + ) + ) { + try { + const xERC20Limits = await getXERC20Limits(tokenConfig, chainMetadata); + logger.info('xERC20 Limits:', xERC20Limits); + updateXERC20LimitsMetrics(xERC20Limits); + } catch (e) { + logger.error('Error checking xERC20 limits', e); + } } }, checkFrequency); } diff --git a/typescript/sdk/src/gas/token-prices.ts b/typescript/sdk/src/gas/token-prices.ts index 26d17cd8bc..d609496308 100644 --- a/typescript/sdk/src/gas/token-prices.ts +++ b/typescript/sdk/src/gas/token-prices.ts @@ -23,23 +23,23 @@ type TokenPriceCacheEntry = { }; class TokenPriceCache { - protected cache: Map; + protected cache: Map; protected freshSeconds: number; protected evictionSeconds: number; constructor(freshSeconds = 60, evictionSeconds = 3 * 60 * 60) { - this.cache = new Map(); + this.cache = new Map(); this.freshSeconds = freshSeconds; this.evictionSeconds = evictionSeconds; } - put(chain: ChainName, price: number): void { + put(id: string, price: number): void { const now = new Date(); - this.cache.set(chain, { timestamp: now, price }); + this.cache.set(id, { timestamp: now, price }); } - isFresh(chain: ChainName): boolean { - const entry = this.cache.get(chain); + isFresh(id: string): boolean { + const entry = this.cache.get(id); if (!entry) return false; const expiryTime = new Date( @@ -49,17 +49,17 @@ class TokenPriceCache { return now < expiryTime; } - fetch(chain: ChainName): number { - const entry = this.cache.get(chain); + fetch(id: string): number { + const entry = this.cache.get(id); if (!entry) { - throw new Error(`no entry found for ${chain} in token price cache`); + throw new Error(`no entry found for ${id} in token price cache`); } const evictionTime = new Date( entry.timestamp.getTime() + 1000 * this.evictionSeconds, ); const now = new Date(); if (now > evictionTime) { - throw new Error(`evicted entry found for ${chain} in token price cache`); + throw new Error(`evicted entry found for ${id} in token price cache`); } return entry.price; } @@ -97,20 +97,30 @@ export class CoinGeckoTokenPriceGetter implements TokenPriceGetter { ); } - async getTokenPrice(chain: ChainName): Promise { - const [price] = await this.getTokenPrices([chain]); + async getTokenPrice( + chain: ChainName, + currency: string = 'usd', + ): Promise { + const [price] = await this.getTokenPrices([chain], currency); return price; } async getTokenExchangeRate( base: ChainName, quote: ChainName, + currency: string = 'usd', ): Promise { - const [basePrice, quotePrice] = await this.getTokenPrices([base, quote]); + const [basePrice, quotePrice] = await this.getTokenPrices( + [base, quote], + currency, + ); return basePrice / quotePrice; } - private async getTokenPrices(chains: ChainName[]): Promise { + private async getTokenPrices( + chains: ChainName[], + currency: string = 'usd', + ): Promise { const isMainnet = chains.map((c) => !this.metadata[c].isTestnet); const allMainnets = isMainnet.every((v) => v === true); const allTestnets = isMainnet.every((v) => v === false); @@ -125,32 +135,43 @@ export class CoinGeckoTokenPriceGetter implements TokenPriceGetter { ); } - const toQuery = chains.filter((c) => !this.cache.isFresh(c)); + const ids = chains.map( + (chain) => this.metadata[chain].gasCurrencyCoinGeckoId || chain, + ); + + await this.getTokenPriceByIds(ids, currency); + return chains.map((chain) => + this.cache.fetch(this.metadata[chain].gasCurrencyCoinGeckoId || chain), + ); + } + + public async getTokenPriceByIds( + ids: string[], + currency: string = 'usd', + ): Promise { + const toQuery = ids.filter((id) => !this.cache.isFresh(id)); + await sleep(this.sleepMsBetweenRequests); + if (toQuery.length > 0) { + let response: any; try { - await this.queryTokenPrices(toQuery); + response = await this.coinGecko.simple.price({ + ids: toQuery, + vs_currencies: [currency], + }); + + if (response.success === true) { + const prices = toQuery.map((id) => response.data[id][currency]); + toQuery.map((id, i) => this.cache.put(id, prices[i])); + } else { + rootLogger.warn('Failed to query token prices', response.message); + return undefined; + } } catch (e) { - rootLogger.warn('Failed to query token prices', e); + rootLogger.warn('Error when querying token prices', e); + return undefined; } } - return chains.map((chain) => this.cache.fetch(chain)); - } - - private async queryTokenPrices(chains: ChainName[]): Promise { - const currency = 'usd'; - // The CoinGecko API expects, in some cases, IDs that do not match - // ChainNames. - const ids = chains.map( - (chain) => this.metadata[chain].gasCurrencyCoinGeckoId || chain, - ); - // Coingecko rate limits, so we are adding this sleep - await sleep(this.sleepMsBetweenRequests); - const response = await this.coinGecko.simple.price({ - ids, - vs_currencies: [currency], - }); - const prices = ids.map((id) => response.data[id][currency]); - // Update the cache with the newly fetched prices - chains.map((chain, i) => this.cache.put(chain, prices[i])); + return ids.map((id) => this.cache.fetch(id)); } } diff --git a/typescript/sdk/src/metadata/warpRouteConfig.ts b/typescript/sdk/src/metadata/warpRouteConfig.ts index 2fd69ce03e..8c1ee346c0 100644 --- a/typescript/sdk/src/metadata/warpRouteConfig.ts +++ b/typescript/sdk/src/metadata/warpRouteConfig.ts @@ -10,6 +10,7 @@ const TokenConfigSchema = z.object({ type: z.nativeEnum(TokenType), hypAddress: z.string(), // HypERC20Collateral, HypERC20Synthetic, HypNativeToken address tokenAddress: z.string().optional(), // external token address needed for collateral type eg tokenAddress.balanceOf(hypAddress) + tokenCoinGeckoId: z.string().optional(), // CoinGecko id for token name: z.string(), symbol: z.string(), decimals: z.number(), diff --git a/yarn.lock b/yarn.lock index 279fe75e60..161abbd887 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7971,7 +7971,7 @@ __metadata: "@ethersproject/providers": "npm:^5.7.2" "@google-cloud/secret-manager": "npm:^5.5.0" "@hyperlane-xyz/helloworld": "npm:5.6.1" - "@hyperlane-xyz/registry": "npm:4.7.0" + "@hyperlane-xyz/registry": "npm:4.10.0" "@hyperlane-xyz/sdk": "npm:5.6.1" "@hyperlane-xyz/utils": "npm:5.6.1" "@inquirer/prompts": "npm:^5.3.8" @@ -8029,6 +8029,16 @@ __metadata: languageName: unknown linkType: soft +"@hyperlane-xyz/registry@npm:4.10.0": + version: 4.10.0 + resolution: "@hyperlane-xyz/registry@npm:4.10.0" + dependencies: + yaml: "npm:2.4.5" + zod: "npm:^3.21.2" + checksum: 22bb18f426cbada8b97db0894fe5d0980dfc08ecbd5174c978b7aeb6d8df9706f93d7e9cf0630644d2455ad05feee714dc2a38ec515a717b0b257184637902fb + languageName: node + linkType: hard + "@hyperlane-xyz/registry@npm:4.7.0": version: 4.7.0 resolution: "@hyperlane-xyz/registry@npm:4.7.0" From a36fc5fb2772e9cb61c5946b6abefb8aeb6c97da Mon Sep 17 00:00:00 2001 From: Tien Dao <15717476+tiendn@users.noreply.github.com> Date: Fri, 25 Oct 2024 22:23:19 +0700 Subject: [PATCH 24/61] fix: isObject utils fn should return only boolean value (#4756) ### Description When `isObject` pass with param `null/undefined` it returns `null/undefined` We should strict return boolean value ### Drive-by changes ```diff export function isObject(item: any): boolean { - return item && typeof item === 'object' && !Array.isArray(item); + return !!item && typeof item === 'object' && !Array.isArray(item); } ``` ### Related issues ### Backward compatibility ### Testing Has updated with test `object.test.ts` --- .changeset/quiet-spoons-sleep.md | 5 +++++ typescript/utils/src/objects.test.ts | 9 +++++++++ typescript/utils/src/objects.ts | 4 ++-- 3 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 .changeset/quiet-spoons-sleep.md diff --git a/.changeset/quiet-spoons-sleep.md b/.changeset/quiet-spoons-sleep.md new file mode 100644 index 0000000000..41bafd9c40 --- /dev/null +++ b/.changeset/quiet-spoons-sleep.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/utils': patch +--- + +fix: isObject utils fn should return only boolean value diff --git a/typescript/utils/src/objects.test.ts b/typescript/utils/src/objects.test.ts index d5ed72a6be..beab06bc38 100644 --- a/typescript/utils/src/objects.test.ts +++ b/typescript/utils/src/objects.test.ts @@ -4,6 +4,7 @@ import { deepCopy, deepEquals, diffObjMerge, + isObject, objMerge, objOmit, } from './objects.js'; @@ -74,6 +75,14 @@ describe('Object utilities', () => { expect(omitted1_2).to.eql({ a: 1, b: { d: 'string' } }); }); + it('isObject', () => { + expect(isObject({})).to.be.true; + expect(isObject([])).to.be.false; + expect(isObject(null)).to.be.false; + expect(isObject(undefined)).to.be.false; + expect(isObject(42)).to.be.false; + }); + describe('diffObjMerge', () => { it('should merge objects with equal values', () => { const actual = { a: 1, b: 2 }; diff --git a/typescript/utils/src/objects.ts b/typescript/utils/src/objects.ts index 403caa849a..1917cbff69 100644 --- a/typescript/utils/src/objects.ts +++ b/typescript/utils/src/objects.ts @@ -5,8 +5,8 @@ import { ethersBigNumberSerializer } from './logging.js'; import { isNullish } from './typeof.js'; import { assert } from './validation.js'; -export function isObject(item: any) { - return item && typeof item === 'object' && !Array.isArray(item); +export function isObject(item: any): boolean { + return !!item && typeof item === 'object' && !Array.isArray(item); } export function deepEquals(v1: any, v2: any) { From 5fd4267e7949f4499c28bf9ef31552a965f30fcd Mon Sep 17 00:00:00 2001 From: Trevor Porter Date: Fri, 25 Oct 2024 16:51:44 +0100 Subject: [PATCH 25/61] fix: ensure recipients on Sealevel warp transfers are 32 bytes (#4760) ### Description Ran into an issue trying to send from Sealevel -> Cosmos where the recipient was 20 bytes. This hasn't been an issue so far because: - no previous Sealevel -> Cosmos transfers have been done in the UI - Sealevel -> EVM transfers are successful bc `addressToBytes` actually pads the 20 byte EVM addresses to 32 bytes - Sealevel -> Sealevel transfers are fine because Sealevel addys are always 32 bytes ### Drive-by changes ### Related issues ### Backward compatibility ### Testing --- .changeset/olive-maps-applaud.md | 6 ++++++ .../sdk/src/token/adapters/SealevelTokenAdapter.ts | 3 ++- typescript/utils/src/addresses.test.ts | 12 ++++++++++++ typescript/utils/src/addresses.ts | 8 ++++++++ typescript/utils/src/index.ts | 1 + 5 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 .changeset/olive-maps-applaud.md diff --git a/.changeset/olive-maps-applaud.md b/.changeset/olive-maps-applaud.md new file mode 100644 index 0000000000..1562fca151 --- /dev/null +++ b/.changeset/olive-maps-applaud.md @@ -0,0 +1,6 @@ +--- +'@hyperlane-xyz/utils': patch +'@hyperlane-xyz/sdk': patch +--- + +Supported non-32 byte non-EVM recipients when sending warps from Sealevel diff --git a/typescript/sdk/src/token/adapters/SealevelTokenAdapter.ts b/typescript/sdk/src/token/adapters/SealevelTokenAdapter.ts index 8287fa07b8..1df0d86375 100644 --- a/typescript/sdk/src/token/adapters/SealevelTokenAdapter.ts +++ b/typescript/sdk/src/token/adapters/SealevelTokenAdapter.ts @@ -21,6 +21,7 @@ import { addressToBytes, eqAddress, median, + padBytesToLength, } from '@hyperlane-xyz/utils'; import { BaseSealevelAdapter } from '../../app/MultiProtocolApp.js'; @@ -295,7 +296,7 @@ export abstract class SealevelHypTokenAdapter instruction: SealevelHypTokenInstruction.TransferRemote, data: new SealevelTransferRemoteInstruction({ destination_domain: destination, - recipient: addressToBytes(recipient), + recipient: padBytesToLength(addressToBytes(recipient), 32), amount_or_id: BigInt(weiAmountOrId), }), }); diff --git a/typescript/utils/src/addresses.test.ts b/typescript/utils/src/addresses.test.ts index d25e33cf8a..2313ce384c 100644 --- a/typescript/utils/src/addresses.test.ts +++ b/typescript/utils/src/addresses.test.ts @@ -4,6 +4,7 @@ import { addressToBytes, bytesToProtocolAddress, isZeroishAddress, + padBytesToLength, } from './addresses.js'; import { ProtocolType } from './types.js'; @@ -42,6 +43,17 @@ describe('Address utilities', () => { }); }); + describe('padBytesToLength', () => { + it('Pads bytes to a given length', () => { + const bytes = Buffer.from([1, 2, 3]); + expect(padBytesToLength(bytes, 5).equals(Buffer.from([0, 0, 1, 2, 3]))); + }); + it('Rejects bytes that exceed the target length', () => { + const bytes = Buffer.from([1, 2, 3]); + expect(() => padBytesToLength(bytes, 2)).to.throw(Error); + }); + }); + describe('bytesToProtocolAddress', () => { it('Converts bytes to address', () => { expect( diff --git a/typescript/utils/src/addresses.ts b/typescript/utils/src/addresses.ts index 88532464fb..01f9fdb107 100644 --- a/typescript/utils/src/addresses.ts +++ b/typescript/utils/src/addresses.ts @@ -316,6 +316,14 @@ export function bytesToBytes32(bytes: Uint8Array): string { ); } +// Pad bytes to a certain length, padding with 0s at the start +export function padBytesToLength(bytes: Uint8Array, length: number) { + if (bytes.length > length) { + throw new Error(`bytes must be ${length} bytes or less`); + } + return Buffer.concat([Buffer.alloc(length - bytes.length), bytes]); +} + export function bytesToAddressEvm(bytes: Uint8Array): Address { return bytes32ToAddress(Buffer.from(bytes).toString('hex')); } diff --git a/typescript/utils/src/index.ts b/typescript/utils/src/index.ts index 0c18543dd6..0c82c6782a 100644 --- a/typescript/utils/src/index.ts +++ b/typescript/utils/src/index.ts @@ -35,6 +35,7 @@ export { normalizeAddressCosmos, normalizeAddressEvm, normalizeAddressSealevel, + padBytesToLength, shortenAddress, strip0x, } from './addresses.js'; From 34222c00fef72e0e92030689839764128c549473 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 25 Oct 2024 16:23:47 +0000 Subject: [PATCH 26/61] Version Packages (#4751) This PR was opened by the [Changesets release](https://github.com/changesets/action) GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated. # Releases ## @hyperlane-xyz/core@5.6.1 ### Patch Changes - a42616ff3: Added overrides for transferFrom, totalSupply to reflect the internal share based accounting for the 4626 mirror asset - Updated dependencies [5fd4267e7] - Updated dependencies [a36fc5fb2] - @hyperlane-xyz/utils@5.6.2 ## @hyperlane-xyz/cli@5.6.2 ### Patch Changes - Updated dependencies [5fd4267e7] - Updated dependencies [a36fc5fb2] - @hyperlane-xyz/utils@5.6.2 - @hyperlane-xyz/sdk@5.6.2 ## @hyperlane-xyz/helloworld@5.6.2 ### Patch Changes - Updated dependencies [5fd4267e7] - Updated dependencies [a42616ff3] - @hyperlane-xyz/sdk@5.6.2 - @hyperlane-xyz/core@5.6.1 ## @hyperlane-xyz/sdk@5.6.2 ### Patch Changes - 5fd4267e7: Supported non-32 byte non-EVM recipients when sending warps from Sealevel - Updated dependencies [5fd4267e7] - Updated dependencies [a36fc5fb2] - Updated dependencies [a42616ff3] - @hyperlane-xyz/utils@5.6.2 - @hyperlane-xyz/core@5.6.1 ## @hyperlane-xyz/utils@5.6.2 ### Patch Changes - 5fd4267e7: Supported non-32 byte non-EVM recipients when sending warps from Sealevel - a36fc5fb2: fix: isObject utils fn should return only boolean value ## @hyperlane-xyz/widgets@5.6.2 ### Patch Changes - Updated dependencies [5fd4267e7] - Updated dependencies [a36fc5fb2] - @hyperlane-xyz/utils@5.6.2 - @hyperlane-xyz/sdk@5.6.2 ## @hyperlane-xyz/infra@5.6.2 ### Patch Changes - Updated dependencies [5fd4267e7] - Updated dependencies [a36fc5fb2] - @hyperlane-xyz/utils@5.6.2 - @hyperlane-xyz/sdk@5.6.2 - @hyperlane-xyz/helloworld@5.6.2 ## @hyperlane-xyz/ccip-server@5.6.2 ## @hyperlane-xyz/github-proxy@5.6.2 --------- Co-authored-by: github-actions[bot] --- .changeset/olive-maps-applaud.md | 6 ----- .changeset/quiet-spoons-sleep.md | 5 ---- .changeset/real-starfishes-fold.md | 5 ---- solidity/CHANGELOG.md | 9 +++++++ solidity/contracts/PackageVersioned.sol | 2 +- solidity/package.json | 4 ++-- typescript/ccip-server/CHANGELOG.md | 2 ++ typescript/ccip-server/package.json | 2 +- typescript/cli/CHANGELOG.md | 9 +++++++ typescript/cli/package.json | 6 ++--- typescript/cli/src/version.ts | 2 +- typescript/github-proxy/CHANGELOG.md | 2 ++ typescript/github-proxy/package.json | 2 +- typescript/helloworld/CHANGELOG.md | 9 +++++++ typescript/helloworld/package.json | 6 ++--- typescript/infra/CHANGELOG.md | 10 ++++++++ typescript/infra/package.json | 8 +++---- typescript/sdk/CHANGELOG.md | 11 +++++++++ typescript/sdk/package.json | 6 ++--- typescript/utils/CHANGELOG.md | 7 ++++++ typescript/utils/package.json | 2 +- typescript/widgets/CHANGELOG.md | 9 +++++++ typescript/widgets/package.json | 6 ++--- yarn.lock | 32 ++++++++++++------------- 24 files changed, 107 insertions(+), 55 deletions(-) delete mode 100644 .changeset/olive-maps-applaud.md delete mode 100644 .changeset/quiet-spoons-sleep.md delete mode 100644 .changeset/real-starfishes-fold.md diff --git a/.changeset/olive-maps-applaud.md b/.changeset/olive-maps-applaud.md deleted file mode 100644 index 1562fca151..0000000000 --- a/.changeset/olive-maps-applaud.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@hyperlane-xyz/utils': patch -'@hyperlane-xyz/sdk': patch ---- - -Supported non-32 byte non-EVM recipients when sending warps from Sealevel diff --git a/.changeset/quiet-spoons-sleep.md b/.changeset/quiet-spoons-sleep.md deleted file mode 100644 index 41bafd9c40..0000000000 --- a/.changeset/quiet-spoons-sleep.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperlane-xyz/utils': patch ---- - -fix: isObject utils fn should return only boolean value diff --git a/.changeset/real-starfishes-fold.md b/.changeset/real-starfishes-fold.md deleted file mode 100644 index f161beae68..0000000000 --- a/.changeset/real-starfishes-fold.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperlane-xyz/core': patch ---- - -Added overrides for transferFrom, totalSupply to reflect the internal share based accounting for the 4626 mirror asset diff --git a/solidity/CHANGELOG.md b/solidity/CHANGELOG.md index fbecf3809c..3bbfb9ebce 100644 --- a/solidity/CHANGELOG.md +++ b/solidity/CHANGELOG.md @@ -1,5 +1,14 @@ # @hyperlane-xyz/core +## 5.6.1 + +### Patch Changes + +- a42616ff3: Added overrides for transferFrom, totalSupply to reflect the internal share based accounting for the 4626 mirror asset +- Updated dependencies [5fd4267e7] +- Updated dependencies [a36fc5fb2] + - @hyperlane-xyz/utils@5.6.2 + ## 5.6.0 ### Minor Changes diff --git a/solidity/contracts/PackageVersioned.sol b/solidity/contracts/PackageVersioned.sol index b1fa39c8ab..44913c2d9c 100644 --- a/solidity/contracts/PackageVersioned.sol +++ b/solidity/contracts/PackageVersioned.sol @@ -7,5 +7,5 @@ pragma solidity >=0.6.11; **/ abstract contract PackageVersioned { // GENERATED CODE - DO NOT EDIT - string public constant PACKAGE_VERSION = "5.6.0"; + string public constant PACKAGE_VERSION = "5.6.1"; } diff --git a/solidity/package.json b/solidity/package.json index f8cbe196c9..5f7eef4420 100644 --- a/solidity/package.json +++ b/solidity/package.json @@ -1,11 +1,11 @@ { "name": "@hyperlane-xyz/core", "description": "Core solidity contracts for Hyperlane", - "version": "5.6.0", + "version": "5.6.1", "dependencies": { "@arbitrum/nitro-contracts": "^1.2.1", "@eth-optimism/contracts": "^0.6.0", - "@hyperlane-xyz/utils": "5.6.1", + "@hyperlane-xyz/utils": "5.6.2", "@layerzerolabs/lz-evm-oapp-v2": "2.0.2", "@openzeppelin/contracts": "^4.9.3", "@openzeppelin/contracts-upgradeable": "^v4.9.3", diff --git a/typescript/ccip-server/CHANGELOG.md b/typescript/ccip-server/CHANGELOG.md index 08febc36fb..96e8357459 100644 --- a/typescript/ccip-server/CHANGELOG.md +++ b/typescript/ccip-server/CHANGELOG.md @@ -1,5 +1,7 @@ # @hyperlane-xyz/ccip-server +## 5.6.2 + ## 5.6.1 ## 5.6.0 diff --git a/typescript/ccip-server/package.json b/typescript/ccip-server/package.json index 5a7c9cdf16..35442643cc 100644 --- a/typescript/ccip-server/package.json +++ b/typescript/ccip-server/package.json @@ -1,6 +1,6 @@ { "name": "@hyperlane-xyz/ccip-server", - "version": "5.6.1", + "version": "5.6.2", "description": "CCIP server", "typings": "dist/index.d.ts", "typedocMain": "src/index.ts", diff --git a/typescript/cli/CHANGELOG.md b/typescript/cli/CHANGELOG.md index 37fc5fea7a..dfe8451c34 100644 --- a/typescript/cli/CHANGELOG.md +++ b/typescript/cli/CHANGELOG.md @@ -1,5 +1,14 @@ # @hyperlane-xyz/cli +## 5.6.2 + +### Patch Changes + +- Updated dependencies [5fd4267e7] +- Updated dependencies [a36fc5fb2] + - @hyperlane-xyz/utils@5.6.2 + - @hyperlane-xyz/sdk@5.6.2 + ## 5.6.1 ### Patch Changes diff --git a/typescript/cli/package.json b/typescript/cli/package.json index 9348646b80..50d2d4f49c 100644 --- a/typescript/cli/package.json +++ b/typescript/cli/package.json @@ -1,13 +1,13 @@ { "name": "@hyperlane-xyz/cli", - "version": "5.6.1", + "version": "5.6.2", "description": "A command-line utility for common Hyperlane operations", "dependencies": { "@aws-sdk/client-kms": "^3.577.0", "@aws-sdk/client-s3": "^3.577.0", "@hyperlane-xyz/registry": "4.7.0", - "@hyperlane-xyz/sdk": "5.6.1", - "@hyperlane-xyz/utils": "5.6.1", + "@hyperlane-xyz/sdk": "5.6.2", + "@hyperlane-xyz/utils": "5.6.2", "@inquirer/core": "9.0.10", "@inquirer/figures": "1.0.5", "@inquirer/prompts": "^3.0.0", diff --git a/typescript/cli/src/version.ts b/typescript/cli/src/version.ts index 8937a2bf5f..087c0bddb8 100644 --- a/typescript/cli/src/version.ts +++ b/typescript/cli/src/version.ts @@ -1 +1 @@ -export const VERSION = '5.6.1'; +export const VERSION = '5.6.2'; diff --git a/typescript/github-proxy/CHANGELOG.md b/typescript/github-proxy/CHANGELOG.md index ae6e2a41af..d23057e7df 100644 --- a/typescript/github-proxy/CHANGELOG.md +++ b/typescript/github-proxy/CHANGELOG.md @@ -1,5 +1,7 @@ # @hyperlane-xyz/github-proxy +## 5.6.2 + ## 5.6.1 ## 5.6.0 diff --git a/typescript/github-proxy/package.json b/typescript/github-proxy/package.json index 9f804e01d2..28d122eb48 100644 --- a/typescript/github-proxy/package.json +++ b/typescript/github-proxy/package.json @@ -1,7 +1,7 @@ { "name": "@hyperlane-xyz/github-proxy", "description": "Github proxy that adds the API key to requests", - "version": "5.6.1", + "version": "5.6.2", "private": true, "scripts": { "deploy": "wrangler deploy", diff --git a/typescript/helloworld/CHANGELOG.md b/typescript/helloworld/CHANGELOG.md index 17f77d3d57..171117f07c 100644 --- a/typescript/helloworld/CHANGELOG.md +++ b/typescript/helloworld/CHANGELOG.md @@ -1,5 +1,14 @@ # @hyperlane-xyz/helloworld +## 5.6.2 + +### Patch Changes + +- Updated dependencies [5fd4267e7] +- Updated dependencies [a42616ff3] + - @hyperlane-xyz/sdk@5.6.2 + - @hyperlane-xyz/core@5.6.1 + ## 5.6.1 ### Patch Changes diff --git a/typescript/helloworld/package.json b/typescript/helloworld/package.json index bc404b472b..722eed273c 100644 --- a/typescript/helloworld/package.json +++ b/typescript/helloworld/package.json @@ -1,11 +1,11 @@ { "name": "@hyperlane-xyz/helloworld", "description": "A basic skeleton of an Hyperlane app", - "version": "5.6.1", + "version": "5.6.2", "dependencies": { - "@hyperlane-xyz/core": "5.6.0", + "@hyperlane-xyz/core": "5.6.1", "@hyperlane-xyz/registry": "4.7.0", - "@hyperlane-xyz/sdk": "5.6.1", + "@hyperlane-xyz/sdk": "5.6.2", "@openzeppelin/contracts-upgradeable": "^4.9.3", "ethers": "^5.7.2" }, diff --git a/typescript/infra/CHANGELOG.md b/typescript/infra/CHANGELOG.md index f5f25b3f32..eee6156256 100644 --- a/typescript/infra/CHANGELOG.md +++ b/typescript/infra/CHANGELOG.md @@ -1,5 +1,15 @@ # @hyperlane-xyz/infra +## 5.6.2 + +### Patch Changes + +- Updated dependencies [5fd4267e7] +- Updated dependencies [a36fc5fb2] + - @hyperlane-xyz/utils@5.6.2 + - @hyperlane-xyz/sdk@5.6.2 + - @hyperlane-xyz/helloworld@5.6.2 + ## 5.6.1 ### Patch Changes diff --git a/typescript/infra/package.json b/typescript/infra/package.json index 81361ac85f..c5fb41ac25 100644 --- a/typescript/infra/package.json +++ b/typescript/infra/package.json @@ -1,7 +1,7 @@ { "name": "@hyperlane-xyz/infra", "description": "Infrastructure utilities for the Hyperlane Network", - "version": "5.6.1", + "version": "5.6.2", "dependencies": { "@arbitrum/sdk": "^3.0.0", "@aws-sdk/client-iam": "^3.74.0", @@ -13,10 +13,10 @@ "@ethersproject/hardware-wallets": "^5.7.0", "@ethersproject/providers": "^5.7.2", "@google-cloud/secret-manager": "^5.5.0", - "@hyperlane-xyz/helloworld": "5.6.1", + "@hyperlane-xyz/helloworld": "5.6.2", "@hyperlane-xyz/registry": "4.10.0", - "@hyperlane-xyz/sdk": "5.6.1", - "@hyperlane-xyz/utils": "5.6.1", + "@hyperlane-xyz/sdk": "5.6.2", + "@hyperlane-xyz/utils": "5.6.2", "@inquirer/prompts": "^5.3.8", "@nomiclabs/hardhat-etherscan": "^3.0.3", "@safe-global/api-kit": "1.3.0", diff --git a/typescript/sdk/CHANGELOG.md b/typescript/sdk/CHANGELOG.md index ad33e87c26..a37619fdf4 100644 --- a/typescript/sdk/CHANGELOG.md +++ b/typescript/sdk/CHANGELOG.md @@ -1,5 +1,16 @@ # @hyperlane-xyz/sdk +## 5.6.2 + +### Patch Changes + +- 5fd4267e7: Supported non-32 byte non-EVM recipients when sending warps from Sealevel +- Updated dependencies [5fd4267e7] +- Updated dependencies [a36fc5fb2] +- Updated dependencies [a42616ff3] + - @hyperlane-xyz/utils@5.6.2 + - @hyperlane-xyz/core@5.6.1 + ## 5.6.1 ### Patch Changes diff --git a/typescript/sdk/package.json b/typescript/sdk/package.json index 44e35d35a4..133a579275 100644 --- a/typescript/sdk/package.json +++ b/typescript/sdk/package.json @@ -1,14 +1,14 @@ { "name": "@hyperlane-xyz/sdk", "description": "The official SDK for the Hyperlane Network", - "version": "5.6.1", + "version": "5.6.2", "dependencies": { "@arbitrum/sdk": "^4.0.0", "@aws-sdk/client-s3": "^3.74.0", "@cosmjs/cosmwasm-stargate": "^0.32.4", "@cosmjs/stargate": "^0.32.4", - "@hyperlane-xyz/core": "5.6.0", - "@hyperlane-xyz/utils": "5.6.1", + "@hyperlane-xyz/core": "5.6.1", + "@hyperlane-xyz/utils": "5.6.2", "@safe-global/api-kit": "1.3.0", "@safe-global/protocol-kit": "1.3.0", "@safe-global/safe-deployments": "1.37.8", diff --git a/typescript/utils/CHANGELOG.md b/typescript/utils/CHANGELOG.md index 8575e59fd8..bc910f9f86 100644 --- a/typescript/utils/CHANGELOG.md +++ b/typescript/utils/CHANGELOG.md @@ -1,5 +1,12 @@ # @hyperlane-xyz/utils +## 5.6.2 + +### Patch Changes + +- 5fd4267e7: Supported non-32 byte non-EVM recipients when sending warps from Sealevel +- a36fc5fb2: fix: isObject utils fn should return only boolean value + ## 5.6.1 ## 5.6.0 diff --git a/typescript/utils/package.json b/typescript/utils/package.json index e06c4cf630..6329e12034 100644 --- a/typescript/utils/package.json +++ b/typescript/utils/package.json @@ -1,7 +1,7 @@ { "name": "@hyperlane-xyz/utils", "description": "General utilities and types for the Hyperlane network", - "version": "5.6.1", + "version": "5.6.2", "dependencies": { "@cosmjs/encoding": "^0.32.4", "@solana/web3.js": "^1.78.0", diff --git a/typescript/widgets/CHANGELOG.md b/typescript/widgets/CHANGELOG.md index 0ded083ed6..0371d680ca 100644 --- a/typescript/widgets/CHANGELOG.md +++ b/typescript/widgets/CHANGELOG.md @@ -1,5 +1,14 @@ # @hyperlane-xyz/widgets +## 5.6.2 + +### Patch Changes + +- Updated dependencies [5fd4267e7] +- Updated dependencies [a36fc5fb2] + - @hyperlane-xyz/utils@5.6.2 + - @hyperlane-xyz/sdk@5.6.2 + ## 5.6.1 ### Patch Changes diff --git a/typescript/widgets/package.json b/typescript/widgets/package.json index 5160014c65..20fe43cb15 100644 --- a/typescript/widgets/package.json +++ b/typescript/widgets/package.json @@ -1,15 +1,15 @@ { "name": "@hyperlane-xyz/widgets", "description": "Common react components for Hyperlane projects", - "version": "5.6.1", + "version": "5.6.2", "peerDependencies": { "react": "^18", "react-dom": "^18" }, "dependencies": { "@headlessui/react": "^2.1.8", - "@hyperlane-xyz/sdk": "5.6.1", - "@hyperlane-xyz/utils": "5.6.1", + "@hyperlane-xyz/sdk": "5.6.2", + "@hyperlane-xyz/utils": "5.6.2", "clsx": "^2.1.1", "react-tooltip": "^5.28.0" }, diff --git a/yarn.lock b/yarn.lock index 161abbd887..ecd030eab6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7824,8 +7824,8 @@ __metadata: "@ethersproject/abi": "npm:*" "@ethersproject/providers": "npm:*" "@hyperlane-xyz/registry": "npm:4.7.0" - "@hyperlane-xyz/sdk": "npm:5.6.1" - "@hyperlane-xyz/utils": "npm:5.6.1" + "@hyperlane-xyz/sdk": "npm:5.6.2" + "@hyperlane-xyz/utils": "npm:5.6.2" "@inquirer/core": "npm:9.0.10" "@inquirer/figures": "npm:1.0.5" "@inquirer/prompts": "npm:^3.0.0" @@ -7860,13 +7860,13 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/core@npm:5.6.0, @hyperlane-xyz/core@workspace:solidity": +"@hyperlane-xyz/core@npm:5.6.1, @hyperlane-xyz/core@workspace:solidity": version: 0.0.0-use.local resolution: "@hyperlane-xyz/core@workspace:solidity" dependencies: "@arbitrum/nitro-contracts": "npm:^1.2.1" "@eth-optimism/contracts": "npm:^0.6.0" - "@hyperlane-xyz/utils": "npm:5.6.1" + "@hyperlane-xyz/utils": "npm:5.6.2" "@layerzerolabs/lz-evm-oapp-v2": "npm:2.0.2" "@layerzerolabs/solidity-examples": "npm:^1.1.0" "@nomiclabs/hardhat-ethers": "npm:^2.2.3" @@ -7917,13 +7917,13 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/helloworld@npm:5.6.1, @hyperlane-xyz/helloworld@workspace:typescript/helloworld": +"@hyperlane-xyz/helloworld@npm:5.6.2, @hyperlane-xyz/helloworld@workspace:typescript/helloworld": version: 0.0.0-use.local resolution: "@hyperlane-xyz/helloworld@workspace:typescript/helloworld" dependencies: - "@hyperlane-xyz/core": "npm:5.6.0" + "@hyperlane-xyz/core": "npm:5.6.1" "@hyperlane-xyz/registry": "npm:4.7.0" - "@hyperlane-xyz/sdk": "npm:5.6.1" + "@hyperlane-xyz/sdk": "npm:5.6.2" "@nomiclabs/hardhat-ethers": "npm:^2.2.3" "@nomiclabs/hardhat-waffle": "npm:^2.0.6" "@openzeppelin/contracts-upgradeable": "npm:^4.9.3" @@ -7970,10 +7970,10 @@ __metadata: "@ethersproject/hardware-wallets": "npm:^5.7.0" "@ethersproject/providers": "npm:^5.7.2" "@google-cloud/secret-manager": "npm:^5.5.0" - "@hyperlane-xyz/helloworld": "npm:5.6.1" + "@hyperlane-xyz/helloworld": "npm:5.6.2" "@hyperlane-xyz/registry": "npm:4.10.0" - "@hyperlane-xyz/sdk": "npm:5.6.1" - "@hyperlane-xyz/utils": "npm:5.6.1" + "@hyperlane-xyz/sdk": "npm:5.6.2" + "@hyperlane-xyz/utils": "npm:5.6.2" "@inquirer/prompts": "npm:^5.3.8" "@nomiclabs/hardhat-ethers": "npm:^2.2.3" "@nomiclabs/hardhat-etherscan": "npm:^3.0.3" @@ -8049,7 +8049,7 @@ __metadata: languageName: node linkType: hard -"@hyperlane-xyz/sdk@npm:5.6.1, @hyperlane-xyz/sdk@workspace:typescript/sdk": +"@hyperlane-xyz/sdk@npm:5.6.2, @hyperlane-xyz/sdk@workspace:typescript/sdk": version: 0.0.0-use.local resolution: "@hyperlane-xyz/sdk@workspace:typescript/sdk" dependencies: @@ -8057,8 +8057,8 @@ __metadata: "@aws-sdk/client-s3": "npm:^3.74.0" "@cosmjs/cosmwasm-stargate": "npm:^0.32.4" "@cosmjs/stargate": "npm:^0.32.4" - "@hyperlane-xyz/core": "npm:5.6.0" - "@hyperlane-xyz/utils": "npm:5.6.1" + "@hyperlane-xyz/core": "npm:5.6.1" + "@hyperlane-xyz/utils": "npm:5.6.2" "@nomiclabs/hardhat-ethers": "npm:^2.2.3" "@nomiclabs/hardhat-waffle": "npm:^2.0.6" "@safe-global/api-kit": "npm:1.3.0" @@ -8099,7 +8099,7 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/utils@npm:5.6.1, @hyperlane-xyz/utils@workspace:typescript/utils": +"@hyperlane-xyz/utils@npm:5.6.2, @hyperlane-xyz/utils@workspace:typescript/utils": version: 0.0.0-use.local resolution: "@hyperlane-xyz/utils@workspace:typescript/utils" dependencies: @@ -8125,8 +8125,8 @@ __metadata: dependencies: "@headlessui/react": "npm:^2.1.8" "@hyperlane-xyz/registry": "npm:4.7.0" - "@hyperlane-xyz/sdk": "npm:5.6.1" - "@hyperlane-xyz/utils": "npm:5.6.1" + "@hyperlane-xyz/sdk": "npm:5.6.2" + "@hyperlane-xyz/utils": "npm:5.6.2" "@storybook/addon-essentials": "npm:^7.6.14" "@storybook/addon-interactions": "npm:^7.6.14" "@storybook/addon-links": "npm:^7.6.14" From 892a1d82826e2ca6b25e791259065d9a6dd17952 Mon Sep 17 00:00:00 2001 From: Daniel Savu <23065004+daniel-savu@users.noreply.github.com> Date: Fri, 25 Oct 2024 18:00:51 +0100 Subject: [PATCH 27/61] fix: tokenAddress for native sttia (#4762) ### Description ### Drive-by changes ### Related issues ### Backward compatibility ### Testing --- .../mainnet3/warp/STTIA-eclipsestride-deployments.yaml | 3 +-- typescript/infra/src/warp/helm.ts | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/typescript/infra/config/environments/mainnet3/warp/STTIA-eclipsestride-deployments.yaml b/typescript/infra/config/environments/mainnet3/warp/STTIA-eclipsestride-deployments.yaml index 18e934fffb..9938ad9f98 100644 --- a/typescript/infra/config/environments/mainnet3/warp/STTIA-eclipsestride-deployments.yaml +++ b/typescript/infra/config/environments/mainnet3/warp/STTIA-eclipsestride-deployments.yaml @@ -8,8 +8,7 @@ data: protocolType: cosmos type: collateral hypAddress: stride134axwdlam929m3mar3wv95nvkyep7mr87ravkqcpf8dfe3v0pjlqwrw6ee - # dummy tokenAddress since stTIA is native - tokenAddress: 'stride134axwdlam929m3mar3wv95nvkyep7mr87ravkqcpf8dfe3v0pjlqwrw6ee' + tokenAddress: 'stutia' name: Stride Staked TIA symbol: stTIA decimals: 6 diff --git a/typescript/infra/src/warp/helm.ts b/typescript/infra/src/warp/helm.ts index e7296ad0c6..bbb98a6d58 100644 --- a/typescript/infra/src/warp/helm.ts +++ b/typescript/infra/src/warp/helm.ts @@ -27,7 +27,7 @@ export class WarpRouteMonitorHelmManager extends HelmManager { return { image: { repository: 'gcr.io/abacus-labs-dev/hyperlane-monorepo', - tag: 'ee5ec6b-20241025-133236', + tag: '8e2f616-20241025-163752', }, configFilePath: pathRelativeToMonorepoRoot, fullnameOverride: this.helmReleaseName, From 04108155d84d6ad09b130ff7451b6a42f03e749b Mon Sep 17 00:00:00 2001 From: Tien Dao <15717476+tiendn@users.noreply.github.com> Date: Sun, 27 Oct 2024 21:10:00 +0700 Subject: [PATCH 28/61] fix: Add unit tests for math utility functions and fix median calculation (#4753) ### Description This pull request introduces the following changes to the math utility functions: #### Fix Median Calculation: Corrected the median function to handle even-length arrays properly by averaging the two middle numbers. #### Add Unit Tests: Implemented unit tests for the math utility functions using Chai to ensure their correctness and reliability. The functions tested include: - `median`: Tests for both odd and even-length arrays. - `sum`: Verifies the sum of an array of numbers. - `mean`: Checks the calculation of the mean. - `stdDev`: Validates the standard deviation calculation, including cases with negative numbers. - `randomInt`: Ensures the generated random integer falls within the specified range. ### Drive-by changes - Code: Updated the median function in math.ts to correctly calculate the median for even-length arrays. - Tests: Added a new test file math.test.ts with comprehensive test cases for each function. ### Related issues Fixes [#[4754]](https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/4754) ### Backward compatibility Yes ### Testing - All tests have been executed and passed successfully, confirming the correctness of the functions. - Special attention was given to edge cases, such as arrays with negative numbers and random integer generation. --- .changeset/healthy-boats-lie.md | 5 ++++ typescript/utils/src/math.test.ts | 48 +++++++++++++++++++++++++++++++ typescript/utils/src/math.ts | 2 +- 3 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 .changeset/healthy-boats-lie.md create mode 100644 typescript/utils/src/math.test.ts diff --git a/.changeset/healthy-boats-lie.md b/.changeset/healthy-boats-lie.md new file mode 100644 index 0000000000..2eb1bb0a46 --- /dev/null +++ b/.changeset/healthy-boats-lie.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/utils': patch +--- + +fix median utils func + add test diff --git a/typescript/utils/src/math.test.ts b/typescript/utils/src/math.test.ts new file mode 100644 index 0000000000..e2f484d656 --- /dev/null +++ b/typescript/utils/src/math.test.ts @@ -0,0 +1,48 @@ +import { expect } from 'chai'; + +import { mean, median, randomInt, stdDev, sum } from './math.js'; + +describe('Math Utility Functions', () => { + describe('median', () => { + it('should return the median of an odd-length array', () => { + expect(median([1, 3, 2])).to.equal(2); + }); + + it('should return the median of an even-length array', () => { + expect(median([1, 2, 3, 4])).to.equal(2.5); + }); + + it('should return the median of an even-length array with non sorted numbers', () => { + expect(median([1, 2, 0, 4, 5, 6])).to.equal(3); + }); + }); + + describe('sum', () => { + it('should return the sum of an array', () => { + expect(sum([1, 2, 3, 4])).to.equal(10); + }); + }); + + describe('mean', () => { + it('should return the mean of an array', () => { + expect(mean([1, 2, 3, 4])).to.equal(2.5); + }); + }); + + describe('stdDev', () => { + it('should return the standard deviation of an array', () => { + expect(stdDev([1, 2, 3, 4])).to.be.closeTo(1.118, 0.001); + }); + }); + + describe('randomInt', () => { + it('should return a random integer within the specified range', () => { + const min = 1; + const max = 10; + const result = randomInt(max, min); + expect(result).to.be.at.least(min); + expect(result).to.be.below(max); + expect(result % 1).to.equal(0); // its an integer + }); + }); +}); diff --git a/typescript/utils/src/math.ts b/typescript/utils/src/math.ts index ebca6e75ef..4cc71bf80f 100644 --- a/typescript/utils/src/math.ts +++ b/typescript/utils/src/math.ts @@ -2,7 +2,7 @@ export function median(a: number[]): number { const sorted = a.slice().sort(); const mid = Math.floor(sorted.length / 2); const median = - sorted.length % 2 == 0 ? (sorted[mid] + sorted[mid + 1]) / 2 : sorted[mid]; + sorted.length % 2 == 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid]; return median; } From cd666d5d0c179a50590b4399ddc5d8589be1d7d7 Mon Sep 17 00:00:00 2001 From: Trevor Porter Date: Mon, 28 Oct 2024 13:08:14 +0000 Subject: [PATCH 29/61] feat: add TIA and stTIA ATA payer observability (#4763) ### Description Adds TIA and stTIA ATA payer observability ### Drive-by changes ### Related issues ### Backward compatibility ### Testing --- .../infra/config/environments/mainnet3/funding.ts | 2 +- .../infra/scripts/funding/fund-keys-from-deployer.ts | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/typescript/infra/config/environments/mainnet3/funding.ts b/typescript/infra/config/environments/mainnet3/funding.ts index e69298dc37..89e34339a9 100644 --- a/typescript/infra/config/environments/mainnet3/funding.ts +++ b/typescript/infra/config/environments/mainnet3/funding.ts @@ -10,7 +10,7 @@ export const keyFunderConfig: KeyFunderConfig< > = { docker: { repo: 'gcr.io/abacus-labs-dev/hyperlane-monorepo', - tag: '436988a-20241017-151047', + tag: '25ae727-20241025-165028', }, // We're currently using the same deployer/key funder key as mainnet2. // To minimize nonce clobbering we offset the key funder cron diff --git a/typescript/infra/scripts/funding/fund-keys-from-deployer.ts b/typescript/infra/scripts/funding/fund-keys-from-deployer.ts index d262e52c87..5eebaab1e5 100644 --- a/typescript/infra/scripts/funding/fund-keys-from-deployer.ts +++ b/typescript/infra/scripts/funding/fund-keys-from-deployer.ts @@ -138,6 +138,16 @@ const sealevelAccountsToTrack: ChainMap = { pubkey: new PublicKey('CijxTbPs9JZxTUfo8Hmz2imxzHtKnDFD3kZP3RPy34uJ'), walletName: 'SOL/eclipsemainnet-solanamainnet/ata-payer', }, + { + // stTIA warp route ATA payer + pubkey: new PublicKey('Bg3bAM3gEhdam5mbPqkiMi3mLZkoAieakMRdMHo6mbcn'), + walletName: 'stTIA/eclipsemainnet-stride/ata-payer', + }, + { + // TIA warp route ATA payer + pubkey: new PublicKey('AZs4Rw6H6YwJBKoHBCfChCitHnHvQcVGgrJwGh4bKmAf'), + walletName: 'TIA/eclipsemainnet-stride/ata-payer', + }, ], }; From 5dabdf38876cfa77f60a858a4d6d53c80e5b0b18 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Mon, 28 Oct 2024 11:00:51 -0400 Subject: [PATCH 30/61] feat: do not exhaustively derive routing config in TS relayer (#4765) ### Description CLI commands using HyperlaneRelayer (`--relay`) were very slow due to exhaustive routing ISM/hook derivation. This short-circuits the derivation when there is a known "message context" to evaluate the routing hook and ISMs for. ### Related issues ### Backward compatibility Yes ### Testing Manual --- .changeset/cold-dingos-give.md | 5 +++++ typescript/sdk/src/core/HyperlaneRelayer.ts | 20 ++++++++++++++++---- typescript/sdk/src/hook/EvmHookReader.ts | 8 +++++++- typescript/sdk/src/ism/EvmIsmReader.ts | 9 +++++++-- 4 files changed, 35 insertions(+), 7 deletions(-) create mode 100644 .changeset/cold-dingos-give.md diff --git a/.changeset/cold-dingos-give.md b/.changeset/cold-dingos-give.md new file mode 100644 index 0000000000..d9eaf08d3d --- /dev/null +++ b/.changeset/cold-dingos-give.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/sdk': patch +--- + +Optimize HyperlaneRelayer routing config derivation diff --git a/typescript/sdk/src/core/HyperlaneRelayer.ts b/typescript/sdk/src/core/HyperlaneRelayer.ts index 27341a773f..cb0ba570e9 100644 --- a/typescript/sdk/src/core/HyperlaneRelayer.ts +++ b/typescript/sdk/src/core/HyperlaneRelayer.ts @@ -75,12 +75,18 @@ export class HyperlaneRelayer { async getHookConfig( chain: ChainName, hook: Address, + messageContext?: DispatchedMessage, ): Promise { let config: DerivedHookConfig | undefined; if (this.cache?.hook[chain]?.[hook]) { config = this.cache.hook[chain][hook] as DerivedHookConfig | undefined; } else { - const evmHookReader = new EvmHookReader(this.multiProvider, chain); + const evmHookReader = new EvmHookReader( + this.multiProvider, + chain, + undefined, + messageContext, + ); config = await evmHookReader.deriveHookConfig(hook); } @@ -98,12 +104,18 @@ export class HyperlaneRelayer { async getIsmConfig( chain: ChainName, ism: Address, + messageContext?: DispatchedMessage, ): Promise { let config: DerivedIsmConfig | undefined; if (this.cache?.ism[chain]?.[ism]) { config = this.cache.ism[chain][ism] as DerivedIsmConfig | undefined; } else { - const evmIsmReader = new EvmIsmReader(this.multiProvider, chain); + const evmIsmReader = new EvmIsmReader( + this.multiProvider, + chain, + undefined, + messageContext, + ); config = await evmIsmReader.deriveIsmConfig(ism); } @@ -124,7 +136,7 @@ export class HyperlaneRelayer { ): Promise { const originChain = this.core.getOrigin(message); const hook = await this.core.getSenderHookAddress(message); - return this.getHookConfig(originChain, hook); + return this.getHookConfig(originChain, hook, message); } async getRecipientIsmConfig( @@ -132,7 +144,7 @@ export class HyperlaneRelayer { ): Promise { const destinationChain = this.core.getDestination(message); const ism = await this.core.getRecipientIsmAddress(message); - return this.getIsmConfig(destinationChain, ism); + return this.getIsmConfig(destinationChain, ism, message); } async relayMessage( diff --git a/typescript/sdk/src/hook/EvmHookReader.ts b/typescript/sdk/src/hook/EvmHookReader.ts index 518b63e585..1ed1f42ab7 100644 --- a/typescript/sdk/src/hook/EvmHookReader.ts +++ b/typescript/sdk/src/hook/EvmHookReader.ts @@ -26,6 +26,7 @@ import { } from '@hyperlane-xyz/utils'; import { DEFAULT_CONTRACT_READ_CONCURRENCY } from '../consts/concurrency.js'; +import { DispatchedMessage } from '../core/types.js'; import { MultiProvider } from '../providers/MultiProvider.js'; import { ChainNameOrId } from '../types.js'; import { HyperlaneReader } from '../utils/HyperlaneReader.js'; @@ -96,6 +97,7 @@ export class EvmHookReader extends HyperlaneReader implements HookReader { protected readonly concurrency: number = multiProvider.tryGetRpcConcurrency( chain, ) ?? DEFAULT_CONTRACT_READ_CONCURRENCY, + protected readonly messageContext?: DispatchedMessage, ) { super(multiProvider, chain); } @@ -379,6 +381,7 @@ export class EvmHookReader extends HyperlaneReader implements HookReader { address: Address, ): Promise> { const hook = DomainRoutingHook__factory.connect(address, this.provider); + this.assertHookType(await hook.hookType(), OnchainHookType.ROUTING); const owner = await hook.owner(); @@ -403,6 +406,7 @@ export class EvmHookReader extends HyperlaneReader implements HookReader { address, this.provider, ); + this.assertHookType( await hook.hookType(), OnchainHookType.FALLBACK_ROUTING, @@ -430,7 +434,9 @@ export class EvmHookReader extends HyperlaneReader implements HookReader { private async fetchDomainHooks( hook: DomainRoutingHook | FallbackDomainRoutingHook, ): Promise { - const domainIds = this.multiProvider.getKnownDomainIds(); + const domainIds = this.messageContext + ? [this.messageContext.parsed.destination] + : this.multiProvider.getKnownDomainIds(); const domainHooks: RoutingHookConfig['domains'] = {}; await concurrentMap(this.concurrency, domainIds, async (domainId) => { diff --git a/typescript/sdk/src/ism/EvmIsmReader.ts b/typescript/sdk/src/ism/EvmIsmReader.ts index c7d766e8ca..47e58d2b76 100644 --- a/typescript/sdk/src/ism/EvmIsmReader.ts +++ b/typescript/sdk/src/ism/EvmIsmReader.ts @@ -1,4 +1,4 @@ -import { ethers } from 'ethers'; +import { BigNumber, ethers } from 'ethers'; import { ArbL2ToL1Ism__factory, @@ -21,6 +21,7 @@ import { } from '@hyperlane-xyz/utils'; import { DEFAULT_CONTRACT_READ_CONCURRENCY } from '../consts/concurrency.js'; +import { DispatchedMessage } from '../core/types.js'; import { MultiProvider } from '../providers/MultiProvider.js'; import { ChainNameOrId } from '../types.js'; import { HyperlaneReader } from '../utils/HyperlaneReader.js'; @@ -66,6 +67,7 @@ export class EvmIsmReader extends HyperlaneReader implements IsmReader { protected readonly concurrency: number = multiProvider.tryGetRpcConcurrency( chain, ) ?? DEFAULT_CONTRACT_READ_CONCURRENCY, + protected readonly messageContext?: DispatchedMessage, ) { super(multiProvider, chain); } @@ -129,11 +131,14 @@ export class EvmIsmReader extends HyperlaneReader implements IsmReader { address, this.provider, ); + const owner = await ism.owner(); this.assertModuleType(await ism.moduleType(), ModuleType.ROUTING); + const domainIds = this.messageContext + ? [BigNumber.from(this.messageContext.parsed.origin)] + : await ism.domains(); const domains: RoutingIsmConfig['domains'] = {}; - const domainIds = await ism.domains(); await concurrentMap(this.concurrency, domainIds, async (domainId) => { const chainName = this.multiProvider.tryGetChainName(domainId.toNumber()); From c622bfbcf54fec17a54662a62f063b31f7807096 Mon Sep 17 00:00:00 2001 From: Tien Dao <15717476+tiendn@users.noreply.github.com> Date: Mon, 28 Oct 2024 22:36:26 +0700 Subject: [PATCH 31/61] test: Coverage more @hyperlane-xyz/utils test (#4758) ### Description Add more coverage `utils` package test ### Drive-by changes ### Related issues ### Backward compatibility ### Testing More unittests function --- typescript/utils/package.json | 3 + typescript/utils/src/arrays.test.ts | 54 +++++++++ typescript/utils/src/async.test.ts | 146 +++++++++++++++++++++++ typescript/utils/src/base58.test.ts | 37 ++++++ typescript/utils/src/base64.test.ts | 74 ++++++++++++ typescript/utils/src/checkpoints.test.ts | 120 +++++++++++++++++++ typescript/utils/src/checkpoints.ts | 2 +- typescript/utils/src/env.test.ts | 18 +++ typescript/utils/src/ids.test.ts | 52 ++++++++ typescript/utils/src/logging.test.ts | 39 ++++++ typescript/utils/src/math.test.ts | 5 + typescript/utils/src/sets.test.ts | 39 ++++++ typescript/utils/src/strings.test.ts | 57 +++++++++ typescript/utils/src/typeof.test.ts | 42 +++++++ typescript/utils/src/validation.test.ts | 13 ++ typescript/utils/src/yaml.test.ts | 33 +++++ yarn.lock | 3 + 17 files changed, 736 insertions(+), 1 deletion(-) create mode 100644 typescript/utils/src/arrays.test.ts create mode 100644 typescript/utils/src/async.test.ts create mode 100644 typescript/utils/src/base58.test.ts create mode 100644 typescript/utils/src/base64.test.ts create mode 100644 typescript/utils/src/checkpoints.test.ts create mode 100644 typescript/utils/src/env.test.ts create mode 100644 typescript/utils/src/ids.test.ts create mode 100644 typescript/utils/src/logging.test.ts create mode 100644 typescript/utils/src/sets.test.ts create mode 100644 typescript/utils/src/strings.test.ts create mode 100644 typescript/utils/src/typeof.test.ts create mode 100644 typescript/utils/src/validation.test.ts create mode 100644 typescript/utils/src/yaml.test.ts diff --git a/typescript/utils/package.json b/typescript/utils/package.json index 6329e12034..e3b85b1e83 100644 --- a/typescript/utils/package.json +++ b/typescript/utils/package.json @@ -14,9 +14,12 @@ "devDependencies": { "@types/lodash-es": "^4.17.12", "@types/mocha": "^10.0.1", + "@types/sinon": "^17.0.1", + "@types/sinon-chai": "^3.2.12", "chai": "4.5.0", "mocha": "^10.2.0", "prettier": "^2.8.8", + "sinon": "^13.0.2", "typescript": "5.3.3" }, "homepage": "https://www.hyperlane.xyz", diff --git a/typescript/utils/src/arrays.test.ts b/typescript/utils/src/arrays.test.ts new file mode 100644 index 0000000000..88273591bd --- /dev/null +++ b/typescript/utils/src/arrays.test.ts @@ -0,0 +1,54 @@ +import { expect } from 'chai'; + +import { chunk, exclude, randomElement } from './arrays.js'; + +describe('Arrays utilities', () => { + describe('chunk', () => { + it('should split an array into chunks of the specified size', () => { + const result = chunk([1, 2, 3, 4, 5], 2); + expect(result).to.deep.equal([[1, 2], [3, 4], [5]]); + }); + + it('should return an empty array when input is empty', () => { + const result = chunk([], 2); + expect(result).to.deep.equal([]); + }); + + it('should handle chunk size larger than array length', () => { + const result = chunk([1, 2], 5); + expect(result).to.deep.equal([[1, 2]]); + }); + }); + + describe('exclude', () => { + it('should exclude the specified item from the list', () => { + const result = exclude(2, [1, 2, 3, 2]); + expect(result).to.deep.equal([1, 3]); + }); + + it('should return the same list if item is not found', () => { + const result = exclude(4, [1, 2, 3]); + expect(result).to.deep.equal([1, 2, 3]); + }); + + it('should return an empty list if all items are excluded', () => { + const result = exclude(1, [1, 1, 1]); + expect(result).to.deep.equal([]); + }); + }); + + describe('randomElement', () => { + beforeEach(() => {}); + + it('should return a random element from the list', () => { + const list = [10, 20, 30]; + const result = randomElement(list); + expect(result).to.be.oneOf(list); + }); + + it('should handle an empty list gracefully', () => { + const result = randomElement([]); + expect(result).to.be.undefined; + }); + }); +}); diff --git a/typescript/utils/src/async.test.ts b/typescript/utils/src/async.test.ts new file mode 100644 index 0000000000..004abb1d18 --- /dev/null +++ b/typescript/utils/src/async.test.ts @@ -0,0 +1,146 @@ +import { expect } from 'chai'; + +import { + concurrentMap, + fetchWithTimeout, + pollAsync, + raceWithContext, + retryAsync, + runWithTimeout, + sleep, + timeout, +} from './async.js'; + +describe('Async Utilities', () => { + describe('sleep', () => { + it('should resolve after sleep duration', async () => { + const start = Date.now(); + await sleep(100); + const duration = Date.now() - start; + expect(duration).to.be.at.least(100); + expect(duration).to.be.lessThan(200); + }); + }); + + describe('timeout', () => { + it('should timeout a promise', async () => { + const promise = new Promise((resolve) => setTimeout(resolve, 200)); + try { + await timeout(promise, 100); + throw new Error('Expected timeout error'); + } catch (error: any) { + expect(error.message).to.equal('Timeout reached'); + } + }); + }); + + describe('runWithTimeout', () => { + it('should run a callback with a timeout', async () => { + const result = await runWithTimeout(100, async () => { + await sleep(50); + return 'success'; + }); + expect(result).to.equal('success'); + }); + }); + + describe('fetchWithTimeout', () => { + it('should fetch with timeout', async () => { + // Mock fetch for testing + global.fetch = async () => { + await sleep(50); + return new Response('ok'); + }; + + const response = await fetchWithTimeout('https://example.com', {}, 100); + expect(await response.text()).to.equal('ok'); + }); + }); + + describe('retryAsync', () => { + it('should retry async function with exponential backoff', async () => { + let attempt = 0; + const runner = async () => { + attempt++; + if (attempt < 3) throw new Error('fail'); + return 'success'; + }; + + const result = await retryAsync(runner, 5, 10); + expect(result).to.equal('success'); + }); + }); + + describe('pollAsync', () => { + it('should poll async function until success', async () => { + let attempt = 0; + const runner = async () => { + attempt++; + if (attempt < 3) throw new Error('fail'); + return 'success'; + }; + + const result = await pollAsync(runner, 10, 5); + expect(result).to.equal('success'); + }); + + it('should fail after reaching max retries', async () => { + let attempt = 0; + const runner = async () => { + attempt++; + throw new Error('fail'); + }; + + try { + await pollAsync(runner, 10, 3); // Set maxAttempts to 3 + throw new Error('Expected pollAsync to throw an error'); + } catch (error: any) { + expect(attempt).to.equal(3); // Ensure it attempted 3 times + expect(error.message).to.equal('fail'); + } + }); + }); + + describe('raceWithContext', () => { + it('should race with context', async () => { + const promises = [ + sleep(50).then(() => 'first'), + sleep(100).then(() => 'second'), + ]; + + const result = await raceWithContext(promises); + expect(result.resolved).to.equal('first'); + expect(result.index).to.equal(0); + }); + }); + + describe('concurrentMap', () => { + it('should map concurrently with correct results', async () => { + const xs = [1, 2, 3, 4, 5, 6]; + const mapFn = async (val: number) => { + await new Promise((resolve) => setTimeout(resolve, 50)); // Simulate async work + return val * 2; + }; + const result = await concurrentMap(2, xs, mapFn); + expect(result).to.deep.equal([2, 4, 6, 8, 10, 12]); + }); + + it('should respect concurrency limit', async () => { + const xs = [1, 2, 3, 4, 5, 6]; + const concurrency = 2; + let activeTasks = 0; + let maxActiveTasks = 0; + + const mapFn = async (val: number) => { + activeTasks++; + maxActiveTasks = Math.max(maxActiveTasks, activeTasks); + await new Promise((resolve) => setTimeout(resolve, 50)); // Simulate async work + activeTasks--; + return val * 2; + }; + + await concurrentMap(concurrency, xs, mapFn); + expect(maxActiveTasks).to.equal(concurrency); + }); + }); +}); diff --git a/typescript/utils/src/base58.test.ts b/typescript/utils/src/base58.test.ts new file mode 100644 index 0000000000..45416c787d --- /dev/null +++ b/typescript/utils/src/base58.test.ts @@ -0,0 +1,37 @@ +import { expect } from 'chai'; +import { utils } from 'ethers'; + +import { base58ToBuffer, bufferToBase58, hexOrBase58ToHex } from './base58.js'; + +describe('Base58 Utilities', () => { + describe('base58ToBuffer', () => { + it('should convert a base58 string to a buffer', () => { + const base58String = '3mJr7AoUXx2Wqd'; + const expectedBuffer = Buffer.from(utils.base58.decode(base58String)); + expect(base58ToBuffer(base58String)).to.deep.equal(expectedBuffer); + }); + }); + + describe('bufferToBase58', () => { + it('should convert a buffer to a base58 string', () => { + const buffer = Buffer.from([1, 2, 3, 4]); + const expectedBase58String = utils.base58.encode(buffer); + expect(bufferToBase58(buffer)).to.equal(expectedBase58String); + }); + }); + + describe('hexOrBase58ToHex', () => { + it('should return the hex string as is if it starts with 0x', () => { + const hexString = '0x1234abcd'; + expect(hexOrBase58ToHex(hexString)).to.equal(hexString); + }); + + it('should convert a base58 string to a hex string', () => { + const base58String = '3mJr7AoUXx2Wqd'; + const expectedHexString = utils.hexlify( + Buffer.from(utils.base58.decode(base58String)), + ); + expect(hexOrBase58ToHex(base58String)).to.equal(expectedHexString); + }); + }); +}); diff --git a/typescript/utils/src/base64.test.ts b/typescript/utils/src/base64.test.ts new file mode 100644 index 0000000000..ca6cfc1516 --- /dev/null +++ b/typescript/utils/src/base64.test.ts @@ -0,0 +1,74 @@ +import { expect } from 'chai'; +import Sinon from 'sinon'; + +import { fromBase64, toBase64 } from './base64.js'; +import { rootLogger } from './logging.js'; + +describe('Base64 Utility Functions', () => { + let loggerStub: sinon.SinonStub; + + beforeEach(() => { + loggerStub = Sinon.stub(rootLogger, 'error'); + }); + + afterEach(() => { + loggerStub.restore(); + }); + + describe('toBase64', () => { + it('should encode a valid object to a base64 string', () => { + const data = { key: 'value' }; + const result = toBase64(data); + expect(result).to.be.a('string'); + expect(result).to.equal(btoa(JSON.stringify(data))); + }); + + it('should return undefined for null or undefined input', () => { + expect(toBase64(null)).to.be.undefined; + expect(toBase64(undefined)).to.be.undefined; + }); + + it('should log an error for invalid input', () => { + toBase64(null); + expect(loggerStub.calledOnce).to.be.true; + expect( + loggerStub.calledWith( + 'Unable to serialize + encode data to base64', + null, + ), + ).to.be.true; + }); + }); + + describe('fromBase64', () => { + it('should decode a valid base64 string to an object', () => { + const data = { key: 'value' }; + const base64String = btoa(JSON.stringify(data)); + const result = fromBase64(base64String); + expect(result).to.deep.equal(data); + }); + + it('should return undefined for null or undefined input', () => { + expect(fromBase64(null as any)).to.be.undefined; + expect(fromBase64(undefined as any)).to.be.undefined; + }); + + it('should handle array input and decode the first element', () => { + const data = { key: 'value' }; + const base64String = btoa(JSON.stringify(data)); + const result = fromBase64([base64String, 'anotherString']); + expect(result).to.deep.equal(data); + }); + + it('should log an error for invalid base64 input', () => { + fromBase64('invalidBase64'); + expect(loggerStub.calledOnce).to.be.true; + expect( + loggerStub.calledWith( + 'Unable to decode + deserialize data from base64', + 'invalidBase64', + ), + ).to.be.true; + }); + }); +}); diff --git a/typescript/utils/src/checkpoints.test.ts b/typescript/utils/src/checkpoints.test.ts new file mode 100644 index 0000000000..c35f73b9db --- /dev/null +++ b/typescript/utils/src/checkpoints.test.ts @@ -0,0 +1,120 @@ +import { expect } from 'chai'; + +import { + isCheckpoint, + isS3Checkpoint, + isS3CheckpointWithId, + isValidSignature, +} from './checkpoints.js'; +import { Checkpoint, S3Checkpoint, S3CheckpointWithId } from './types.js'; + +describe('Checkpoints', () => { + describe('isValidSignature', () => { + it('should return true for valid string signature', () => { + const signature = '0x' + 'a'.repeat(130); // Example of a valid hex string + expect(isValidSignature(signature)).to.be.true; + }); + + it('should return true for valid object signature', () => { + const signature = { + r: '0x' + 'a'.repeat(64), + s: '0x' + 'b'.repeat(64), + v: 27, + }; + expect(isValidSignature(signature)).to.be.true; + }); + + it('should return false for invalid signature', () => { + const signature = { + r: '0x' + 'a'.repeat(64), + s: '0x' + 'b'.repeat(64), + v: 'invalid', + }; + expect(isValidSignature(signature)).to.be.false; + }); + }); + + describe('isCheckpoint', () => { + it('should return true for valid checkpoint', () => { + const checkpoint: Checkpoint = { + root: '0x' + 'a'.repeat(64), + index: 1, + merkle_tree_hook_address: '0x' + 'b'.repeat(40), + mailbox_domain: 123, + }; + expect(isCheckpoint(checkpoint)).to.be.true; + }); + + it('should return false for invalid checkpoint', () => { + const checkpoint = { + root: 'invalid', + index: 'invalid', + merkle_tree_hook_address: 'invalid', + mailbox_domain: 'invalid', + }; + expect(isCheckpoint(checkpoint)).to.be.false; + }); + }); + + describe('isS3Checkpoint', () => { + it('should return true for valid S3Checkpoint', () => { + const s3Checkpoint: S3Checkpoint = { + signature: '0x' + 'a'.repeat(130), + value: { + root: '0x' + 'a'.repeat(64), + index: 1, + merkle_tree_hook_address: '0x' + 'b'.repeat(40), + mailbox_domain: 123, + }, + }; + expect(isS3Checkpoint(s3Checkpoint)).to.be.true; + }); + + it('should return false for invalid S3Checkpoint', () => { + const s3Checkpoint = { + signature: 'invalid', + value: { + root: 'invalid', + index: 'invalid', + merkle_tree_hook_address: 'invalid', + mailbox_domain: 'invalid', + }, + }; + expect(isS3Checkpoint(s3Checkpoint)).to.be.false; + }); + }); + + describe('isS3CheckpointWithId', () => { + it('should return true for valid S3CheckpointWithId', () => { + const s3CheckpointWithId: S3CheckpointWithId = { + signature: '0x' + 'a'.repeat(130), + value: { + checkpoint: { + root: '0x' + 'a'.repeat(64), + index: 1, + merkle_tree_hook_address: '0x' + 'b'.repeat(40), + mailbox_domain: 123, + }, + message_id: '0x' + 'c'.repeat(64), + }, + }; + expect(isS3CheckpointWithId(s3CheckpointWithId)).to.be.true; + }); + + it('should return false for invalid S3CheckpointWithId', () => { + const s3CheckpointWithId = { + signature: 'invalid', + value: { + checkpoint: { + root: 'invalid', + index: 'invalid', + merkle_tree_hook_address: 'invalid', + mailbox_domain: 'invalid', + }, + message_id: 'invalid', + }, + }; + expect(isS3CheckpointWithId(s3CheckpointWithId)).to.be.false; + }); + }); +}); diff --git a/typescript/utils/src/checkpoints.ts b/typescript/utils/src/checkpoints.ts index d83d54c686..88afc705eb 100644 --- a/typescript/utils/src/checkpoints.ts +++ b/typescript/utils/src/checkpoints.ts @@ -7,7 +7,7 @@ import { SignatureLike, } from './types.js'; -function isValidSignature(signature: any): signature is SignatureLike { +export function isValidSignature(signature: any): signature is SignatureLike { return typeof signature === 'string' ? utils.isHexString(signature) : utils.isHexString(signature.r) && diff --git a/typescript/utils/src/env.test.ts b/typescript/utils/src/env.test.ts new file mode 100644 index 0000000000..3772fcb4d1 --- /dev/null +++ b/typescript/utils/src/env.test.ts @@ -0,0 +1,18 @@ +import { expect } from 'chai'; + +import { safelyAccessEnvVar } from './env.js'; + +describe('Env Utilities', () => { + describe('safelyAccessEnvVar', () => { + it('should return the environment variable', () => { + process.env.TEST_VAR = '0xTEST_VAR'; + expect(safelyAccessEnvVar('TEST_VAR')).to.equal('0xTEST_VAR'); + expect(safelyAccessEnvVar('TEST_VAR', true)).to.equal('0xtest_var'); + }); + + it('should return undefined if the environment variable is not set', () => { + expect(safelyAccessEnvVar('NON_EXISTENT_VAR')).to.be.undefined; + expect(safelyAccessEnvVar('NON_EXISTENT_VAR', true)).to.be.undefined; + }); + }); +}); diff --git a/typescript/utils/src/ids.test.ts b/typescript/utils/src/ids.test.ts new file mode 100644 index 0000000000..5008eeaf12 --- /dev/null +++ b/typescript/utils/src/ids.test.ts @@ -0,0 +1,52 @@ +import { expect } from 'chai'; +import { utils } from 'ethers'; + +import { canonizeId, evmId } from './ids.js'; + +describe('ID Utilities', () => { + describe('canonizeId', () => { + it('should convert a 20-byte ID to a 32-byte ID', () => { + const id = '0x1234567890123456789012345678901234567890'; + const result = canonizeId(id); + expect(result).to.be.instanceOf(Uint8Array); + expect(result.length).to.equal(32); + expect(utils.hexlify(result)).to.equal( + '0x0000000000000000000000001234567890123456789012345678901234567890', + ); + }); + + it('should throw an error for IDs longer than 32 bytes', () => { + const id = '0x' + '12'.repeat(33); + expect(() => canonizeId(id)).to.throw('Too long'); + }); + + it('should throw an error for IDs not 20 or 32 bytes', () => { + const id = '0x1234567890'; + expect(() => canonizeId(id)).to.throw( + 'bad input, expect address or bytes32', + ); + }); + }); + + describe('evmId', () => { + it('should convert a 32-byte ID to a 20-byte EVM address', () => { + const id = + '0x' + '00'.repeat(12) + '1234567890123456789012345678901234567890'; + const result = evmId(id); + expect(result).to.equal('0x1234567890123456789012345678901234567890'); + }); + + it('should return the same 20-byte ID as a 20-byte EVM address', () => { + const id = '0x1234567890123456789012345678901234567890'; + const result = evmId(id); + expect(result).to.equal(id); + }); + + it('should throw an error for IDs not 20 or 32 bytes', () => { + const id = '0x1234567890'; + expect(() => evmId(id)).to.throw( + 'Invalid id length. expected 20 or 32. Got 5', + ); + }); + }); +}); diff --git a/typescript/utils/src/logging.test.ts b/typescript/utils/src/logging.test.ts new file mode 100644 index 0000000000..812ac8f5a5 --- /dev/null +++ b/typescript/utils/src/logging.test.ts @@ -0,0 +1,39 @@ +import { expect } from 'chai'; +import { BigNumber } from 'ethers'; + +import { ethersBigNumberSerializer } from './logging.js'; + +describe('Logging Utilities', () => { + describe('ethersBigNumberSerializer', () => { + it('should serialize a BigNumber object correctly', () => { + const key = 'testKey'; + const value = { + type: 'BigNumber', + hex: '0x1a', + }; + const result = ethersBigNumberSerializer(key, value); + expect(result).to.equal(BigNumber.from(value.hex).toString()); + }); + + it('should return the value unchanged if it is not a BigNumber', () => { + const key = 'testKey'; + const value = { some: 'object' }; + const result = ethersBigNumberSerializer(key, value); + expect(result).to.equal(value); + }); + + it('should return the value unchanged if it is null', () => { + const key = 'testKey'; + const value = null; + const result = ethersBigNumberSerializer(key, value); + expect(result).to.equal(value); + }); + + it('should return the value unchanged if it is not an object', () => { + const key = 'testKey'; + const value = 'string'; + const result = ethersBigNumberSerializer(key, value); + expect(result).to.equal(value); + }); + }); +}); diff --git a/typescript/utils/src/math.test.ts b/typescript/utils/src/math.test.ts index e2f484d656..85cbee5880 100644 --- a/typescript/utils/src/math.test.ts +++ b/typescript/utils/src/math.test.ts @@ -20,6 +20,7 @@ describe('Math Utility Functions', () => { describe('sum', () => { it('should return the sum of an array', () => { expect(sum([1, 2, 3, 4])).to.equal(10); + expect(sum([1, -2, 3, 4])).to.equal(6); }); }); @@ -33,6 +34,10 @@ describe('Math Utility Functions', () => { it('should return the standard deviation of an array', () => { expect(stdDev([1, 2, 3, 4])).to.be.closeTo(1.118, 0.001); }); + + it('should return the standard deviation of an array with negative numbers', () => { + expect(stdDev([-1, -2, -3, -4])).to.be.closeTo(1.118, 0.001); + }); }); describe('randomInt', () => { diff --git a/typescript/utils/src/sets.test.ts b/typescript/utils/src/sets.test.ts new file mode 100644 index 0000000000..3f4195b4df --- /dev/null +++ b/typescript/utils/src/sets.test.ts @@ -0,0 +1,39 @@ +import { expect } from 'chai'; + +import { difference, setEquality, symmetricDifference } from './sets.js'; + +describe('Set Operations', () => { + describe('difference', () => { + it('should return the difference of two sets', () => { + const setA = new Set([1, 2, 3, undefined]); + const setB = new Set([2, 3, 4]); + const result = difference(setA, setB); + expect(result).to.deep.equal(new Set([1, undefined])); + }); + }); + + describe('symmetricDifference', () => { + it('should return the symmetric difference of two sets', () => { + const setA = new Set([1, 2, 3]); + const setB = new Set([2, 3, 4]); + const result = symmetricDifference(setA, setB); + expect(result).to.deep.equal(new Set([1, 4])); + }); + }); + + describe('setEquality', () => { + it('should return true for equal sets', () => { + const setA = new Set([1, 2, 3]); + const setB = new Set([1, 2, 3]); + const result = setEquality(setA, setB); + expect(result).to.be.true; + }); + + it('should return false for non-equal sets', () => { + const setA = new Set([1, 2, 3]); + const setB = new Set([1, 2, 4]); + const result = setEquality(setA, setB); + expect(result).to.be.false; + }); + }); +}); diff --git a/typescript/utils/src/strings.test.ts b/typescript/utils/src/strings.test.ts new file mode 100644 index 0000000000..3cf4798231 --- /dev/null +++ b/typescript/utils/src/strings.test.ts @@ -0,0 +1,57 @@ +import { expect } from 'chai'; +import { Readable } from 'stream'; + +import { + errorToString, + fromHexString, + sanitizeString, + streamToString, + toHexString, + toTitleCase, + trimToLength, +} from './strings.js'; + +describe('String Utilities', () => { + it('should convert string to title case', () => { + expect(toTitleCase('hello world')).to.equal('Hello World'); + expect(toTitleCase('HELLO WORLD')).to.equal('Hello World'); + expect(toTitleCase('4ELLO WORLD')).to.equal('4ello World'); + expect(toTitleCase('')).to.equal(''); + }); + + it('should sanitize string by removing non-alphanumeric characters', () => { + expect(sanitizeString('Hello, World!')).to.equal('helloworld'); + expect(sanitizeString('123-456')).to.equal('123456'); + expect(sanitizeString('')).to.equal(''); + }); + + it('should trim string to specified length', () => { + expect(trimToLength('Hello, World!', 5)).to.equal('Hello...'); + expect(trimToLength('Short', 10)).to.equal('Short'); + expect(trimToLength('', 10)).to.equal(''); + }); + + it('should convert stream to string', async () => { + const stream = new Readable(); + stream.push('Hello, '); + stream.push('World!'); + stream.push(null); + + const result = await streamToString(stream); + expect(result).to.equal('Hello, World!'); + }); + + it('should convert error to string', () => { + expect(errorToString('Error message')).to.equal('Error message'); + expect(errorToString({ message: 'Error object' })).to.equal('Error object'); + expect(errorToString(404)).to.equal('Error code: 404'); + expect(errorToString(null)).to.equal('Unknown Error'); + }); + + it('should convert hex string to buffer and back', () => { + const hexString = '0x48656c6c6f'; + const buffer = fromHexString(hexString); + expect(buffer.toString('utf8')).to.equal('Hello'); + expect(toHexString(buffer)).to.equal(hexString); + }); +}); diff --git a/typescript/utils/src/typeof.test.ts b/typescript/utils/src/typeof.test.ts new file mode 100644 index 0000000000..31382a374e --- /dev/null +++ b/typescript/utils/src/typeof.test.ts @@ -0,0 +1,42 @@ +import { expect } from 'chai'; + +import { isNullish, isNumeric } from './typeof.js'; + +describe('isNullish', () => { + it('should return true for null', () => { + expect(isNullish(null)).to.be.true; + }); + + it('should return true for undefined', () => { + expect(isNullish(undefined)).to.be.true; + }); + + it('should return false for non-nullish values', () => { + expect(isNullish('')).to.be.false; + expect(isNullish(0)).to.be.false; + expect(isNullish(false)).to.be.false; + }); +}); + +describe('isNumeric', () => { + it('should return true for numeric strings', () => { + expect(isNumeric('123')).to.be.true; + }); + + it('should return true for numbers', () => { + expect(isNumeric(123)).to.be.true; + }); + + it('should return true for negative numbers', () => { + expect(isNumeric(-123)).to.be.true; + }); + + it('should return true for floating point numbers', () => { + expect(isNumeric(123.45)).to.be.true; + }); + + it('should return false for non-numeric strings', () => { + expect(isNumeric('abc')).to.be.false; + expect(isNumeric('123abc')).to.be.false; + }); +}); diff --git a/typescript/utils/src/validation.test.ts b/typescript/utils/src/validation.test.ts new file mode 100644 index 0000000000..7521a4ebf0 --- /dev/null +++ b/typescript/utils/src/validation.test.ts @@ -0,0 +1,13 @@ +import { expect } from 'chai'; + +import { assert } from './validation.js'; + +describe('assert', () => { + it('should not throw an error when the predicate is true', () => { + expect(() => assert(true, 'Error message')).to.not.throw(); + }); + + it('should throw an error when the predicate is false', () => { + expect(() => assert(false, 'Error message')).to.throw('Error message'); + }); +}); diff --git a/typescript/utils/src/yaml.test.ts b/typescript/utils/src/yaml.test.ts new file mode 100644 index 0000000000..3e69dd3094 --- /dev/null +++ b/typescript/utils/src/yaml.test.ts @@ -0,0 +1,33 @@ +import { expect } from 'chai'; + +import { tryParseJsonOrYaml } from './yaml.js'; + +describe('tryParseJsonOrYaml', () => { + it('should parse valid JSON string', () => { + const jsonString = '{"key": "value"}'; + const result: any = tryParseJsonOrYaml(jsonString); + expect(result.success).to.be.true; + expect(result.data).to.deep.equal({ key: 'value' }); + }); + + it('should parse valid YAML string', () => { + const yamlString = 'key: value'; + const result: any = tryParseJsonOrYaml(yamlString); + expect(result.success).to.be.true; + expect(result.data).to.deep.equal({ key: 'value' }); + }); + + it('should fail for invalid JSON string', () => { + const invalidJsonString = '{"key": "value"'; + const result: any = tryParseJsonOrYaml(invalidJsonString); + expect(result.success).to.be.false; + expect(result.error).to.equal('Input is not valid JSON or YAML'); + }); + + it('should fail for invalid YAML string', () => { + const invalidYamlString = 'key: value:'; + const result: any = tryParseJsonOrYaml(invalidYamlString); + expect(result.success).to.be.false; + expect(result.error).to.equal('Input is not valid JSON or YAML'); + }); +}); diff --git a/yarn.lock b/yarn.lock index ecd030eab6..884d2b2087 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8107,6 +8107,8 @@ __metadata: "@solana/web3.js": "npm:^1.78.0" "@types/lodash-es": "npm:^4.17.12" "@types/mocha": "npm:^10.0.1" + "@types/sinon": "npm:^17.0.1" + "@types/sinon-chai": "npm:^3.2.12" bignumber.js: "npm:^9.1.1" chai: "npm:4.5.0" ethers: "npm:^5.7.2" @@ -8114,6 +8116,7 @@ __metadata: mocha: "npm:^10.2.0" pino: "npm:^8.19.0" prettier: "npm:^2.8.8" + sinon: "npm:^13.0.2" typescript: "npm:5.3.3" yaml: "npm:2.4.5" languageName: unknown From e104cf6aa31bffc1bf026140cfaa5f6e50febf75 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Mon, 28 Oct 2024 13:28:12 -0400 Subject: [PATCH 32/61] refactor: dedupe HyperlaneIsmFactory and IsmModule.create (#4732) ### Description - Uses HyperlaneIsmFactory in IsmModuleCreate for deduping redundant code ### Backward compatibility Yes ### Testing Unit tests --- .changeset/dirty-swans-drum.md | 6 + typescript/sdk/src/contracts/contracts.ts | 31 +- typescript/sdk/src/core/EvmCoreModule.ts | 15 +- .../sdk/src/deploy/EvmModuleDeployer.ts | 359 --------------- .../sdk/src/deploy/HyperlaneDeployer.ts | 5 +- typescript/sdk/src/hook/EvmHookModule.ts | 150 +++---- typescript/sdk/src/ism/EvmIsmModule.ts | 417 ++---------------- .../ism/HyperlaneIsmFactory.hardhat-test.ts | 1 - typescript/sdk/src/ism/HyperlaneIsmFactory.ts | 85 ++-- .../sdk/src/token/EvmERC20WarpModule.ts | 15 +- typescript/utils/src/index.ts | 11 +- typescript/utils/src/sets.ts | 10 + 12 files changed, 237 insertions(+), 868 deletions(-) create mode 100644 .changeset/dirty-swans-drum.md delete mode 100644 typescript/sdk/src/deploy/EvmModuleDeployer.ts diff --git a/.changeset/dirty-swans-drum.md b/.changeset/dirty-swans-drum.md new file mode 100644 index 0000000000..963f91b395 --- /dev/null +++ b/.changeset/dirty-swans-drum.md @@ -0,0 +1,6 @@ +--- +'@hyperlane-xyz/utils': patch +'@hyperlane-xyz/sdk': patch +--- + +Dedupe internals of hook and ISM module deploy code diff --git a/typescript/sdk/src/contracts/contracts.ts b/typescript/sdk/src/contracts/contracts.ts index 5f1913a556..aadeba0854 100644 --- a/typescript/sdk/src/contracts/contracts.ts +++ b/typescript/sdk/src/contracts/contracts.ts @@ -1,10 +1,11 @@ import { Contract } from 'ethers'; -import { Ownable } from '@hyperlane-xyz/core'; +import { Ownable, Ownable__factory } from '@hyperlane-xyz/core'; import { Address, ProtocolType, ValueOf, + eqAddress, hexOrBase58ToHex, objFilter, objMap, @@ -12,8 +13,10 @@ import { promiseObjAll, } from '@hyperlane-xyz/utils'; +import { OwnableConfig } from '../deploy/types.js'; import { ChainMetadataManager } from '../metadata/ChainMetadataManager.js'; import { MultiProvider } from '../providers/MultiProvider.js'; +import { AnnotatedEV5Transaction } from '../providers/ProviderType.js'; import { ChainMap, Connection } from '../types.js'; import { @@ -257,3 +260,29 @@ export function appFromAddressesMapHelper( multiProvider, }; } + +export function transferOwnershipTransactions( + chainId: number, + contract: Address, + actual: OwnableConfig, + expected: OwnableConfig, + label?: string, +): AnnotatedEV5Transaction[] { + if (eqAddress(actual.owner, expected.owner)) { + return []; + } + + return [ + { + chainId, + annotation: `Transferring ownership of ${label ?? contract} from ${ + actual.owner + } to ${expected.owner}`, + to: contract, + data: Ownable__factory.createInterface().encodeFunctionData( + 'transferOwnership', + [expected.owner], + ), + }, + ]; +} diff --git a/typescript/sdk/src/core/EvmCoreModule.ts b/typescript/sdk/src/core/EvmCoreModule.ts index fd4e96018b..1be81d98f9 100644 --- a/typescript/sdk/src/core/EvmCoreModule.ts +++ b/typescript/sdk/src/core/EvmCoreModule.ts @@ -10,6 +10,7 @@ import { import { attachContractsMap, serializeContractsMap, + transferOwnershipTransactions, } from '../contracts/contracts.js'; import { HyperlaneAddresses, @@ -17,7 +18,6 @@ import { } from '../contracts/types.js'; import { DeployedCoreAddresses } from '../core/schemas.js'; import { CoreConfig } from '../core/types.js'; -import { EvmModuleDeployer } from '../deploy/EvmModuleDeployer.js'; import { HyperlaneProxyFactoryDeployer } from '../deploy/HyperlaneProxyFactoryDeployer.js'; import { ProxyFactoryFactories, @@ -202,12 +202,13 @@ export class EvmCoreModule extends HyperlaneModule< actualConfig: CoreConfig, expectedConfig: CoreConfig, ): AnnotatedEV5Transaction[] { - return EvmModuleDeployer.createTransferOwnershipTx({ - actualOwner: actualConfig.owner, - expectedOwner: expectedConfig.owner, - deployedAddress: this.args.addresses.mailbox, - chainId: this.domainId, - }); + return transferOwnershipTransactions( + this.domainId, + this.args.addresses.mailbox, + actualConfig, + expectedConfig, + 'Mailbox', + ); } /** diff --git a/typescript/sdk/src/deploy/EvmModuleDeployer.ts b/typescript/sdk/src/deploy/EvmModuleDeployer.ts deleted file mode 100644 index 6b2a09eba9..0000000000 --- a/typescript/sdk/src/deploy/EvmModuleDeployer.ts +++ /dev/null @@ -1,359 +0,0 @@ -import { ethers } from 'ethers'; -import { Logger } from 'pino'; - -import { - Ownable__factory, - StaticAddressSetFactory, - StaticThresholdAddressSetFactory, - TransparentUpgradeableProxy__factory, -} from '@hyperlane-xyz/core'; -import { buildArtifact as coreBuildArtifact } from '@hyperlane-xyz/core/buildArtifact.js'; -import { - Address, - addBufferToGasLimit, - eqAddress, - rootLogger, -} from '@hyperlane-xyz/utils'; - -import { HyperlaneContracts, HyperlaneFactories } from '../contracts/types.js'; -import { MultiProvider } from '../providers/MultiProvider.js'; -import { AnnotatedEV5Transaction } from '../providers/ProviderType.js'; -import { ChainMap, ChainName } from '../types.js'; - -import { isProxy, proxyConstructorArgs } from './proxy.js'; -import { ContractVerifier } from './verify/ContractVerifier.js'; -import { - ContractVerificationInput, - ExplorerLicenseType, -} from './verify/types.js'; -import { getContractVerificationInput } from './verify/utils.js'; - -export class EvmModuleDeployer { - public verificationInputs: ChainMap = {}; - - constructor( - protected readonly multiProvider: MultiProvider, - protected readonly factories: Factories, - protected readonly logger = rootLogger.child({ - module: 'EvmModuleDeployer', - }), - protected readonly contractVerifier?: ContractVerifier, - ) { - this.contractVerifier ??= new ContractVerifier( - multiProvider, - {}, - coreBuildArtifact, - ExplorerLicenseType.MIT, - ); - } - - // Deploys a contract from a factory - public async deployContractFromFactory({ - chain, - factory, - contractName, - constructorArgs, - initializeArgs, - implementationAddress, - }: { - chain: ChainName; - factory: F; - contractName: string; - constructorArgs: Parameters; - initializeArgs?: Parameters>['initialize']>; - implementationAddress?: Address; - }): Promise> { - this.logger.info( - `Deploying ${contractName} on ${chain} with constructor args (${constructorArgs.join( - ', ', - )})...`, - ); - const contract = await this.multiProvider.handleDeploy( - chain, - factory, - constructorArgs, - ); - - if (initializeArgs) { - this.logger.debug(`Initialize ${contractName} on ${chain}`); - // Estimate gas for the initialize transaction - const estimatedGas = await contract.estimateGas.initialize( - ...initializeArgs, - ); - - // deploy with buffer on gas limit - const overrides = this.multiProvider.getTransactionOverrides(chain); - const initTx = await contract.initialize(...initializeArgs, { - gasLimit: addBufferToGasLimit(estimatedGas), - ...overrides, - }); - - await this.multiProvider.handleTx(chain, initTx); - } - - const verificationInput = getContractVerificationInput({ - name: contractName, - contract, - bytecode: factory.bytecode, - expectedimplementation: implementationAddress, - }); - this.addVerificationArtifacts({ chain, artifacts: [verificationInput] }); - - // try verifying contract - try { - await this.contractVerifier?.verifyContract(chain, verificationInput); - } catch (error) { - // log error but keep deploying, can also verify post-deployment if needed - this.logger.debug(`Error verifying contract: ${error}`); - } - - return contract; - } - - /** - * Deploys a contract with a specified name. - * - * This function is capable of deploying any contract type defined within the `Factories` type to a specified chain. - * - * @param {ChainName} chain - The name of the chain on which the contract is to be deployed. - * @param {K} contractKey - The key identifying the factory to use for deployment. - * @param {string} contractName - The name of the contract to deploy. This must match the contract source code. - * @param {Parameters} constructorArgs - Arguments for the contract's constructor. - * @param {Parameters>['initialize']>?} initializeArgs - Optional arguments for the contract's initialization function. - * @returns {Promise[K]>} A promise that resolves to the deployed contract instance. - */ - public async deployContractWithName({ - chain, - contractKey, - contractName, - constructorArgs, - initializeArgs, - }: { - chain: ChainName; - contractKey: K; - contractName: string; - constructorArgs: Parameters; - initializeArgs?: Parameters< - Awaited>['initialize'] - >; - }): Promise[K]> { - const contract = await this.deployContractFromFactory({ - chain, - factory: this.factories[contractKey], - contractName, - constructorArgs, - initializeArgs, - }); - return contract; - } - - // Deploys a contract with the same name as the contract key - public async deployContract({ - chain, - contractKey, - constructorArgs, - initializeArgs, - }: { - chain: ChainName; - contractKey: K; - constructorArgs: Parameters; - initializeArgs?: Parameters< - Awaited>['initialize'] - >; - }): Promise[K]> { - return this.deployContractWithName({ - chain, - contractKey, - contractName: contractKey.toString(), - constructorArgs, - initializeArgs, - }); - } - - // Deploys the Implementation and Proxy for a given contract - public async deployProxiedContract({ - chain, - contractKey, - contractName, - proxyAdmin, - constructorArgs, - initializeArgs, - }: { - chain: ChainName; - contractKey: K; - contractName: string; - proxyAdmin: string; - constructorArgs: Parameters; - initializeArgs?: Parameters[K]['initialize']>; - }): Promise[K]> { - // Try to initialize the implementation even though it may not be necessary - const implementation = await this.deployContractWithName({ - chain, - contractKey, - contractName, - constructorArgs, - initializeArgs, - }); - - // Initialize the proxy the same way - return this.deployProxy({ - chain, - implementation, - proxyAdmin, - initializeArgs, - }); - } - - // Deploys a proxy for a given implementation contract - protected async deployProxy({ - chain, - implementation, - proxyAdmin, - initializeArgs, - }: { - chain: ChainName; - implementation: C; - proxyAdmin: string; - initializeArgs?: Parameters; - }): Promise { - const isProxied = await isProxy( - this.multiProvider.getProvider(chain), - implementation.address, - ); - if (isProxied) { - // if the implementation is already a proxy, do not deploy a new proxy - return implementation; - } - - const constructorArgs = proxyConstructorArgs( - implementation, - proxyAdmin, - initializeArgs, - ); - const proxy = await this.deployContractFromFactory({ - chain, - factory: new TransparentUpgradeableProxy__factory(), - contractName: 'TransparentUpgradeableProxy', - constructorArgs, - implementationAddress: implementation.address, - }); - - return implementation.attach(proxy.address) as C; - } - - // Adds verification artifacts to the verificationInputs map - protected addVerificationArtifacts({ - chain, - artifacts, - }: { - chain: ChainName; - artifacts: ContractVerificationInput[]; - }): void { - this.verificationInputs[chain] = this.verificationInputs[chain] || []; - artifacts.forEach((artifact) => { - this.verificationInputs[chain].push(artifact); - }); - } - - // Static deploy function used by Hook and ISM modules. - public static async deployStaticAddressSet({ - chain, - factory, - values, - logger, - threshold = values.length, - multiProvider, - }: { - chain: ChainName; - factory: StaticThresholdAddressSetFactory | StaticAddressSetFactory; - values: Address[]; - logger: Logger; - threshold?: number; - multiProvider: MultiProvider; - }): Promise
{ - const sortedValues = [...values].sort(); - - const address = await factory['getAddress(address[],uint8)']( - sortedValues, - threshold, - ); - const code = await multiProvider.getProvider(chain).getCode(address); - if (code === '0x') { - logger.debug( - `Deploying new ${threshold} of ${sortedValues.length} address set to ${chain}`, - ); - const overrides = multiProvider.getTransactionOverrides(chain); - - // estimate gas - const estimatedGas = await factory.estimateGas['deploy(address[],uint8)']( - sortedValues, - threshold, - overrides, - ); - - // add gas buffer - const hash = await factory['deploy(address[],uint8)']( - sortedValues, - threshold, - { - gasLimit: addBufferToGasLimit(estimatedGas), - ...overrides, - }, - ); - - await multiProvider.handleTx(chain, hash); - } else { - logger.debug( - `Recovered ${threshold} of ${sortedValues.length} address set on ${chain}: ${address}`, - ); - } - - // TODO: figure out how to get the constructor arguments for manual deploy TXs - // const verificationInput = buildVerificationInput( - // NAME, - // ADDRESS, - // CONSTRUCTOR_ARGS, - // ); - // await this.deployer.verifyContract( - // this.chainName, - // verificationInput, - // logger, - // ); - - return address; - } - - /** - * Transfers ownership of a contract to a new owner. - * - * @param actualOwner - The current owner of the contract. - * @param expectedOwner - The expected new owner of the contract. - * @param deployedAddress - The address of the deployed contract. - * @param chainId - The chain ID of the network the contract is deployed on. - * @returns An array of annotated EV5 transactions that need to be executed to update the owner. - */ - public static createTransferOwnershipTx(params: { - actualOwner: Address; - expectedOwner: Address; - deployedAddress: Address; - chainId: number; - }): AnnotatedEV5Transaction[] { - const { actualOwner, expectedOwner, deployedAddress, chainId } = params; - const updateTransactions: AnnotatedEV5Transaction[] = []; - if (eqAddress(actualOwner, expectedOwner)) { - return []; - } - - updateTransactions.push({ - annotation: `Transferring ownership of ${deployedAddress} from current owner ${actualOwner} to new owner ${expectedOwner}`, - chainId, - to: deployedAddress, - data: Ownable__factory.createInterface().encodeFunctionData( - 'transferOwnership(address)', - [expectedOwner], - ), - }); - - return updateTransactions; - } -} diff --git a/typescript/sdk/src/deploy/HyperlaneDeployer.ts b/typescript/sdk/src/deploy/HyperlaneDeployer.ts index 8203c79192..c6cd2048c6 100644 --- a/typescript/sdk/src/deploy/HyperlaneDeployer.ts +++ b/typescript/sdk/src/deploy/HyperlaneDeployer.ts @@ -74,6 +74,8 @@ export abstract class HyperlaneDeployer< public cachedAddresses: HyperlaneAddressesMap = {}; public deployedContracts: HyperlaneContractsMap = {}; + protected cachingEnabled = true; + protected logger: Logger; chainTimeoutMs: number; @@ -86,7 +88,6 @@ export abstract class HyperlaneDeployer< ) { this.logger = options?.logger ?? rootLogger.child({ module: 'deployer' }); this.chainTimeoutMs = options?.chainTimeoutMs ?? 15 * 60 * 1000; // 15 minute timeout per chain - this.options.ismFactory?.setDeployer(this); if (Object.keys(icaAddresses).length > 0) { this.options.icaApp = InterchainAccount.fromAddressesMap( icaAddresses, @@ -374,7 +375,7 @@ export abstract class HyperlaneDeployer< shouldRecover = true, implementationAddress?: Address, ): Promise> { - if (shouldRecover) { + if (this.cachingEnabled && shouldRecover) { const cachedContract = this.readCache(chain, factory, contractName); if (cachedContract) { if (this.recoverVerificationInputs) { diff --git a/typescript/sdk/src/hook/EvmHookModule.ts b/typescript/sdk/src/hook/EvmHookModule.ts index fb8e128422..a1b6d14120 100644 --- a/typescript/sdk/src/hook/EvmHookModule.ts +++ b/typescript/sdk/src/hook/EvmHookModule.ts @@ -40,16 +40,16 @@ import { HyperlaneModuleParams, } from '../core/AbstractHyperlaneModule.js'; import { CoreAddresses } from '../core/contracts.js'; -import { EvmModuleDeployer } from '../deploy/EvmModuleDeployer.js'; +import { HyperlaneDeployer } from '../deploy/HyperlaneDeployer.js'; import { ProxyFactoryFactories } from '../deploy/contracts.js'; import { ContractVerifier } from '../deploy/verify/ContractVerifier.js'; -import { IgpFactories, igpFactories } from '../gas/contracts.js'; import { IgpConfig } from '../gas/types.js'; import { EvmIsmModule } from '../ism/EvmIsmModule.js'; +import { HyperlaneIsmFactory } from '../ism/HyperlaneIsmFactory.js'; import { ArbL2ToL1IsmConfig, IsmType, OpStackIsmConfig } from '../ism/types.js'; import { MultiProvider } from '../providers/MultiProvider.js'; import { AnnotatedEV5Transaction } from '../providers/ProviderType.js'; -import { ChainNameOrId } from '../types.js'; +import { ChainName, ChainNameOrId } from '../types.js'; import { normalizeConfig } from '../utils/ism.js'; import { EvmHookReader } from './EvmHookReader.js'; @@ -75,6 +75,14 @@ type HookModuleAddresses = { proxyAdmin: Address; }; +class HookDeployer extends HyperlaneDeployer<{}, HookFactories> { + protected cachingEnabled = false; + + deployContracts(_chain: ChainName, _config: {}): Promise { + throw new Error('Method not implemented.'); + } +} + export class EvmHookModule extends HyperlaneModule< ProtocolType.Ethereum, HookConfig, @@ -82,7 +90,9 @@ export class EvmHookModule extends HyperlaneModule< > { protected readonly logger = rootLogger.child({ module: 'EvmHookModule' }); protected readonly reader: EvmHookReader; - protected readonly deployer: EvmModuleDeployer; + // "ISM" Factory has aggregation hook factories too + protected readonly hookFactory: HyperlaneIsmFactory; + protected readonly deployer: HookDeployer; // Adding these to reduce how often we need to grab from MultiProvider. public readonly chain: string; @@ -105,15 +115,11 @@ export class EvmHookModule extends HyperlaneModule< super(params); this.reader = new EvmHookReader(multiProvider, this.args.chain); - this.deployer = new EvmModuleDeployer( + this.hookFactory = HyperlaneIsmFactory.fromAddressesMap( + { [this.args.chain]: params.addresses }, multiProvider, - { - ...hookFactories, - ...igpFactories, - }, - this.logger, - contractVerifier, ); + this.deployer = new HookDeployer(multiProvider, hookFactories); this.chain = this.multiProvider.getChainName(this.args.chain); this.domainId = this.multiProvider.getDomainId(this.chain); @@ -625,11 +631,9 @@ export class EvmHookModule extends HyperlaneModule< switch (config.type) { case HookType.MERKLE_TREE: - return this.deployer.deployContract({ - chain: this.chain, - contractKey: HookType.MERKLE_TREE, - constructorArgs: [this.args.addresses.mailbox], - }); + return this.deployer.deployContract(this.chain, HookType.MERKLE_TREE, [ + this.args.addresses.mailbox, + ]); case HookType.INTERCHAIN_GAS_PAYMASTER: return this.deployIgpHook({ config }); case HookType.AGGREGATION: @@ -657,16 +661,13 @@ export class EvmHookModule extends HyperlaneModule< config: ProtocolFeeHookConfig; }): Promise { this.logger.debug('Deploying ProtocolFeeHook...'); - return this.deployer.deployContract({ - chain: this.chain, - contractKey: HookType.PROTOCOL_FEE, - constructorArgs: [ - config.maxProtocolFee, - config.protocolFee, - config.beneficiary, - config.owner, - ], - }); + const deployer = new HookDeployer(this.multiProvider, hookFactories); + return deployer.deployContract(this.chain, HookType.PROTOCOL_FEE, [ + config.maxProtocolFee, + config.protocolFee, + config.beneficiary, + config.owner, + ]); } protected async deployPausableHook({ @@ -675,11 +676,12 @@ export class EvmHookModule extends HyperlaneModule< config: PausableHookConfig; }): Promise { this.logger.debug('Deploying PausableHook...'); - const hook = await this.deployer.deployContract({ - chain: this.chain, - contractKey: HookType.PAUSABLE, - constructorArgs: [], - }); + const deployer = new HookDeployer(this.multiProvider, hookFactories); + const hook = await deployer.deployContract( + this.chain, + HookType.PAUSABLE, + [], + ); // transfer ownership await this.multiProvider.handleTx( @@ -715,13 +717,12 @@ export class EvmHookModule extends HyperlaneModule< this.args.addresses.staticAggregationHookFactory, signer, ); - const address = await EvmModuleDeployer.deployStaticAddressSet({ - chain: this.chain, + const address = await this.hookFactory.deployStaticAddressSet( + this.chain, factory, - values: aggregatedHooks, - logger: this.logger, - multiProvider: this.multiProvider, - }); + aggregatedHooks, + this.logger, + ); // return aggregation hook return StaticAggregationHook__factory.connect(address, signer); @@ -773,16 +774,12 @@ export class EvmHookModule extends HyperlaneModule< ); // deploy opstack hook - const hook = await this.deployer.deployContract({ - chain, - contractKey: HookType.OP_STACK, - constructorArgs: [ - mailbox, - this.multiProvider.getDomainId(config.destinationChain), - addressToBytes32(opstackIsm.address), - config.nativeBridge, - ], - }); + const hook = await this.deployer.deployContract(chain, HookType.OP_STACK, [ + mailbox, + this.multiProvider.getDomainId(config.destinationChain), + addressToBytes32(opstackIsm.address), + config.nativeBridge, + ]); // set authorized hook on opstack ism const authorizedHook = await opstackIsm.authorizedHook(); @@ -866,17 +863,17 @@ export class EvmHookModule extends HyperlaneModule< ); // deploy arbL1ToL1 hook - const hook = await this.deployer.deployContract({ + const hook = await this.deployer.deployContract( chain, - contractKey: HookType.ARB_L2_TO_L1, - constructorArgs: [ + HookType.ARB_L2_TO_L1, + [ mailbox, this.multiProvider.getDomainId(config.destinationChain), addressToBytes32(arbL2ToL1IsmAddress), config.arbSys, BigNumber.from(200_000), // 2x estimate of executeTransaction call overhead ], - }); + ); // set authorized hook on arbL2ToL1 ism const authorizedHook = await arbL2ToL1Ism.authorizedHook(); if (authorizedHook === addressToBytes32(hook.address)) { @@ -928,22 +925,18 @@ export class EvmHookModule extends HyperlaneModule< // deploy fallback hook const fallbackHook = await this.deploy({ config: config.fallback }); // deploy routing hook with fallback - routingHook = await this.deployer.deployContract({ - chain: this.chain, - contractKey: HookType.FALLBACK_ROUTING, - constructorArgs: [ - this.args.addresses.mailbox, - deployerAddress, - fallbackHook.address, - ], - }); + routingHook = await this.deployer.deployContract( + this.chain, + HookType.FALLBACK_ROUTING, + [this.args.addresses.mailbox, deployerAddress, fallbackHook.address], + ); } else { // deploy routing hook - routingHook = await this.deployer.deployContract({ - chain: this.chain, - contractKey: HookType.ROUTING, - constructorArgs: [this.args.addresses.mailbox, deployerAddress], - }); + routingHook = await this.deployer.deployContract( + this.chain, + HookType.ROUTING, + [this.args.addresses.mailbox, deployerAddress], + ); } // compute the hooks that need to be set @@ -1002,14 +995,14 @@ export class EvmHookModule extends HyperlaneModule< ); // Deploy the InterchainGasPaymaster - const igp = await this.deployer.deployProxiedContract({ - chain: this.chain, - contractKey: HookType.INTERCHAIN_GAS_PAYMASTER, - contractName: HookType.INTERCHAIN_GAS_PAYMASTER, - proxyAdmin: this.args.addresses.proxyAdmin, - constructorArgs: [], - initializeArgs: [deployerAddress, config.beneficiary], - }); + const igp = await this.deployer.deployProxiedContract( + this.chain, + HookType.INTERCHAIN_GAS_PAYMASTER, + HookType.INTERCHAIN_GAS_PAYMASTER, + this.args.addresses.proxyAdmin, + [], + [deployerAddress, config.beneficiary], + ); // Obtain the transactions to set the gas params for each remote const configureTxs = await this.updateIgpRemoteGasParams({ @@ -1038,11 +1031,12 @@ export class EvmHookModule extends HyperlaneModule< config: IgpConfig; }): Promise { // Deploy the StorageGasOracle, by default msg.sender is the owner - const gasOracle = await this.deployer.deployContract({ - chain: this.chain, - contractKey: 'storageGasOracle', - constructorArgs: [], - }); + const gasOracle = await this.deployer.deployContractFromFactory( + this.chain, + new StorageGasOracle__factory(), + 'storageGasOracle', + [], + ); // Obtain the transactions to set the gas params for each remote const configureTxs = await this.updateStorageGasOracle({ diff --git a/typescript/sdk/src/ism/EvmIsmModule.ts b/typescript/sdk/src/ism/EvmIsmModule.ts index 691ae1deb6..bba38aa570 100644 --- a/typescript/sdk/src/ism/EvmIsmModule.ts +++ b/typescript/sdk/src/ism/EvmIsmModule.ts @@ -1,63 +1,38 @@ import { ethers } from 'ethers'; import { Logger } from 'pino'; -import { - ArbL2ToL1Ism__factory, - DefaultFallbackRoutingIsm__factory, - DomainRoutingIsm, - DomainRoutingIsmFactory__factory, - DomainRoutingIsm__factory, - IAggregationIsm, - IAggregationIsm__factory, - IInterchainSecurityModule__factory, - IMultisigIsm, - IMultisigIsm__factory, - IRoutingIsm, - OPStackIsm__factory, - Ownable__factory, - PausableIsm__factory, - TestIsm__factory, - TrustedRelayerIsm__factory, -} from '@hyperlane-xyz/core'; +import { DomainRoutingIsm__factory } from '@hyperlane-xyz/core'; import { Address, Domain, ProtocolType, - addBufferToGasLimit, assert, deepEquals, - eqAddress, - objFilter, + intersection, rootLogger, } from '@hyperlane-xyz/utils'; -import { attachAndConnectContracts } from '../contracts/contracts.js'; -import { HyperlaneAddresses, HyperlaneContracts } from '../contracts/types.js'; +import { transferOwnershipTransactions } from '../contracts/contracts.js'; +import { HyperlaneAddresses } from '../contracts/types.js'; import { HyperlaneModule, HyperlaneModuleParams, } from '../core/AbstractHyperlaneModule.js'; -import { EvmModuleDeployer } from '../deploy/EvmModuleDeployer.js'; -import { - ProxyFactoryFactories, - proxyFactoryFactories, -} from '../deploy/contracts.js'; +import { ProxyFactoryFactories } from '../deploy/contracts.js'; import { ContractVerifier } from '../deploy/verify/ContractVerifier.js'; import { MultiProvider } from '../providers/MultiProvider.js'; import { AnnotatedEV5Transaction } from '../providers/ProviderType.js'; import { ChainName, ChainNameOrId } from '../types.js'; import { normalizeConfig } from '../utils/ism.js'; -import { findMatchingLogEvents } from '../utils/logUtils.js'; import { EvmIsmReader } from './EvmIsmReader.js'; +import { HyperlaneIsmFactory } from './HyperlaneIsmFactory.js'; import { IsmConfigSchema } from './schemas.js'; import { - AggregationIsmConfig, DeployedIsm, IsmConfig, IsmType, MUTABLE_ISM_TYPE, - MultisigIsmConfig, RoutingIsmConfig, } from './types.js'; import { calculateDomainRoutingDelta } from './utils.js'; @@ -74,8 +49,8 @@ export class EvmIsmModule extends HyperlaneModule< > { protected readonly logger = rootLogger.child({ module: 'EvmIsmModule' }); protected readonly reader: EvmIsmReader; - protected readonly deployer: EvmModuleDeployer; - protected readonly factories: HyperlaneContracts; + protected readonly ismFactory: HyperlaneIsmFactory; + protected readonly mailbox: Address; // Adding these to reduce how often we need to grab from MultiProvider. public readonly chain: ChainName; @@ -95,33 +70,14 @@ export class EvmIsmModule extends HyperlaneModule< super(params); this.reader = new EvmIsmReader(multiProvider, params.chain); - this.deployer = new EvmModuleDeployer( - this.multiProvider, - {}, - this.logger, - contractVerifier, - ); - this.factories = attachAndConnectContracts( - { - staticMerkleRootMultisigIsmFactory: - params.addresses.staticMerkleRootMultisigIsmFactory, - staticMessageIdMultisigIsmFactory: - params.addresses.staticMessageIdMultisigIsmFactory, - staticAggregationIsmFactory: - params.addresses.staticAggregationIsmFactory, - staticAggregationHookFactory: - params.addresses.staticAggregationHookFactory, - domainRoutingIsmFactory: params.addresses.domainRoutingIsmFactory, - staticMerkleRootWeightedMultisigIsmFactory: - params.addresses.staticMerkleRootWeightedMultisigIsmFactory, - staticMessageIdWeightedMultisigIsmFactory: - params.addresses.staticMessageIdWeightedMultisigIsmFactory, - }, - proxyFactoryFactories, - multiProvider.getSigner(params.chain), + this.ismFactory = HyperlaneIsmFactory.fromAddressesMap( + { [params.chain]: params.addresses }, + multiProvider, ); + this.mailbox = params.addresses.mailbox; + this.chain = this.multiProvider.getChainName(this.args.chain); this.domainId = this.multiProvider.getDomainId(this.chain); } @@ -211,24 +167,14 @@ export class EvmIsmModule extends HyperlaneModule< } // Lastly, check if the resolved owner is different from the current owner - const provider = this.multiProvider.getProvider(this.chain); - const owner = await Ownable__factory.connect( - this.args.addresses.deployedIsm, - provider, - ).owner(); - - // Return an ownership transfer transaction if required - if (!eqAddress(targetConfig.owner, owner)) { - updateTxs.push({ - annotation: 'Transferring ownership of ownable ISM...', - chainId: this.domainId, - to: this.args.addresses.deployedIsm, - data: Ownable__factory.createInterface().encodeFunctionData( - 'transferOwnership(address)', - [targetConfig.owner], - ), - }); - } + updateTxs.push( + ...transferOwnershipTransactions( + this.domainId, + this.args.addresses.deployedIsm, + currentConfig, + targetConfig, + ), + ); return updateTxs; } @@ -278,30 +224,24 @@ export class EvmIsmModule extends HyperlaneModule< target: RoutingIsmConfig; logger: Logger; }): Promise { - const routingIsmInterface = DomainRoutingIsm__factory.createInterface(); - const updateTxs = []; - - // filter out domains which are not part of the multiprovider - current = { - ...current, - domains: this.filterRoutingIsmDomains({ - config: current, - }).availableDomains, - }; - target = { - ...target, - domains: this.filterRoutingIsmDomains({ - config: target, - }).availableDomains, - }; + const contract = DomainRoutingIsm__factory.connect( + this.args.addresses.deployedIsm, + this.multiProvider.getProvider(this.chain), + ); + + const updateTxs: AnnotatedEV5Transaction[] = []; + + const knownChains = new Set(this.multiProvider.getKnownChainNames()); const { domainsToEnroll, domainsToUnenroll } = calculateDomainRoutingDelta( current, target, ); + const knownEnrolls = intersection(knownChains, new Set(domainsToEnroll)); + // Enroll domains - for (const origin of domainsToEnroll) { + for (const origin of knownEnrolls) { logger.debug( `Reconfiguring preexisting routing ISM for origin ${origin}...`, ); @@ -310,27 +250,27 @@ export class EvmIsmModule extends HyperlaneModule< }); const domainId = this.multiProvider.getDomainId(origin); + const tx = await contract.populateTransaction.set(domainId, ism.address); updateTxs.push({ annotation: `Setting new ISM for origin ${origin}...`, + ...tx, chainId: this.domainId, - to: this.args.addresses.deployedIsm, - data: routingIsmInterface.encodeFunctionData('set(uint32,address)', [ - domainId, - ism.address, - ]), }); } + const knownUnenrolls = intersection( + knownChains, + new Set(domainsToUnenroll), + ); + // Unenroll domains - for (const origin of domainsToUnenroll) { + for (const origin of knownUnenrolls) { const domainId = this.multiProvider.getDomainId(origin); + const tx = await contract.populateTransaction.remove(domainId); updateTxs.push({ annotation: `Unenrolling originDomain ${domainId} from preexisting routing ISM at ${this.args.addresses.deployedIsm}...`, + ...tx, chainId: this.domainId, - to: this.args.addresses.deployedIsm, - data: routingIsmInterface.encodeFunctionData('remove(uint32)', [ - domainId, - ]), }); } @@ -344,275 +284,10 @@ export class EvmIsmModule extends HyperlaneModule< }): Promise { config = IsmConfigSchema.parse(config); - // If it's an address ISM, just return a base ISM - if (typeof config === 'string') { - // TODO: https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/3773 - // we can remove the ts-ignore once we have a proper type for address ISMs - // @ts-ignore - return IInterchainSecurityModule__factory.connect( - config, - this.multiProvider.getSignerOrProvider(this.args.chain), - ); - } - - const ismType = config.type; - const logger = rootLogger.child({ chainName: this.chain, ismType }); - - logger.debug(`Deploying ${ismType} to ${this.args.chain}`); - - switch (ismType) { - case IsmType.MESSAGE_ID_MULTISIG: - case IsmType.MERKLE_ROOT_MULTISIG: - return this.deployMultisigIsm({ - config, - logger, - }); - - case IsmType.ROUTING: - case IsmType.FALLBACK_ROUTING: - return this.deployRoutingIsm({ - config, - logger, - }); - - case IsmType.AGGREGATION: - return this.deployAggregationIsm({ - config, - logger, - }); - - case IsmType.OP_STACK: - return this.deployer.deployContractFromFactory({ - chain: this.chain, - factory: new OPStackIsm__factory(), - contractName: IsmType.OP_STACK, - constructorArgs: [config.nativeBridge], - }); - - case IsmType.ARB_L2_TO_L1: - return this.deployer.deployContractFromFactory({ - chain: this.chain, - factory: new ArbL2ToL1Ism__factory(), - contractName: IsmType.ARB_L2_TO_L1, - constructorArgs: [config.bridge], - }); - - case IsmType.PAUSABLE: - return this.deployer.deployContractFromFactory({ - chain: this.chain, - factory: new PausableIsm__factory(), - contractName: IsmType.PAUSABLE, - constructorArgs: [config.owner], - }); - - case IsmType.TRUSTED_RELAYER: - assert( - this.args.addresses.mailbox, - `Mailbox address is required for deploying ${ismType}`, - ); - return this.deployer.deployContractFromFactory({ - chain: this.chain, - factory: new TrustedRelayerIsm__factory(), - contractName: IsmType.TRUSTED_RELAYER, - constructorArgs: [this.args.addresses.mailbox, config.relayer], - }); - - case IsmType.TEST_ISM: - return this.deployer.deployContractFromFactory({ - chain: this.chain, - factory: new TestIsm__factory(), - contractName: IsmType.TEST_ISM, - constructorArgs: [], - }); - - default: - throw new Error(`Unsupported ISM type ${ismType}`); - } - } - - protected async deployMultisigIsm({ - config, - logger, - }: { - config: MultisigIsmConfig; - logger: Logger; - }): Promise { - const signer = this.multiProvider.getSigner(this.chain); - const factoryName = - config.type === IsmType.MERKLE_ROOT_MULTISIG - ? 'staticMerkleRootMultisigIsmFactory' - : 'staticMessageIdMultisigIsmFactory'; - - const address = await EvmModuleDeployer.deployStaticAddressSet({ - chain: this.chain, - factory: this.factories[factoryName], - values: config.validators, - logger, - threshold: config.threshold, - multiProvider: this.multiProvider, - }); - - return IMultisigIsm__factory.connect(address, signer); - } - - protected async deployRoutingIsm({ - config, - logger, - }: { - config: RoutingIsmConfig; - logger: Logger; - }): Promise { - // filter out domains which are not part of the multiprovider - const { availableDomains, availableDomainIds } = - this.filterRoutingIsmDomains({ - config, - }); - config = { - ...config, - domains: availableDomains, - }; - - // deploy the submodules first - const submoduleAddresses: Address[] = []; - for (const origin of Object.keys(config.domains)) { - const { address } = await this.deploy({ - config: config.domains[origin], - }); - submoduleAddresses.push(address); - } - - if (config.type === IsmType.FALLBACK_ROUTING) { - // deploy the fallback routing ISM - logger.debug('Deploying fallback routing ISM ...'); - const ism = await this.multiProvider.handleDeploy( - this.chain, - new DefaultFallbackRoutingIsm__factory(), - [this.args.addresses.mailbox], - ); - - // initialize the fallback routing ISM - logger.debug('Initializing fallback routing ISM ...'); - const tx = await ism['initialize(address,uint32[],address[])']( - config.owner, - availableDomainIds, - submoduleAddresses, - this.multiProvider.getTransactionOverrides(this.args.chain), - ); - - await this.multiProvider.handleTx(this.chain, tx); - // return the fallback routing ISM - return ism; - } - - // then deploy the domain routing ISM - logger.debug('Deploying domain routing ISM ...'); - return this.deployDomainRoutingIsm({ - owner: config.owner, - domainIds: availableDomainIds, - submoduleAddresses, - }); - } - - protected async deployDomainRoutingIsm({ - owner, - domainIds, - submoduleAddresses, - }: { - owner: string; - domainIds: number[]; - submoduleAddresses: string[]; - }): Promise { - const overrides = this.multiProvider.getTransactionOverrides( - this.args.chain, - ); - - const signer = this.multiProvider.getSigner(this.args.chain); - const domainRoutingIsmFactory = DomainRoutingIsmFactory__factory.connect( - this.args.addresses.domainRoutingIsmFactory, - signer, - ); - - // estimate gas - const estimatedGas = await domainRoutingIsmFactory.estimateGas.deploy( - owner, - domainIds, - submoduleAddresses, - overrides, - ); - - // deploying new domain routing ISM, add gas buffer - const tx = await domainRoutingIsmFactory.deploy( - owner, - domainIds, - submoduleAddresses, - { - gasLimit: addBufferToGasLimit(estimatedGas), - ...overrides, - }, - ); - - const receipt = await this.multiProvider.handleTx(this.args.chain, tx); - const dispatchLogs = findMatchingLogEvents( - receipt.logs, - domainRoutingIsmFactory.interface, - 'ModuleDeployed', - ); - - if (dispatchLogs.length === 0) { - throw new Error('No ModuleDeployed event found'); - } - - const moduleAddress = dispatchLogs[0].args['module']; - return DomainRoutingIsm__factory.connect(moduleAddress, signer); - } - - protected async deployAggregationIsm({ - config, - logger, - }: { - config: AggregationIsmConfig; - logger: Logger; - }): Promise { - const addresses: Address[] = []; - // Needs to be deployed sequentially because Ethers will throw `Error: replacement fee too low` - for (const module of config.modules) { - const submodule = await this.deploy({ config: module }); - addresses.push(submodule.address); - } - - const factoryName = 'staticAggregationIsmFactory'; - const address = await EvmModuleDeployer.deployStaticAddressSet({ - chain: this.chain, - factory: this.factories[factoryName], - values: addresses, - logger: logger, - threshold: config.threshold, - multiProvider: this.multiProvider, + return this.ismFactory.deploy({ + destination: this.chain, + config, + mailbox: this.mailbox, }); - - const signer = this.multiProvider.getSigner(this.args.chain); - return IAggregationIsm__factory.connect(address, signer); - } - - // filtering out domains which are not part of the multiprovider - private filterRoutingIsmDomains({ config }: { config: RoutingIsmConfig }) { - const availableDomainIds: number[] = []; - const availableDomains = objFilter( - config.domains, - (domain, _): _ is IsmConfig => { - const domainId = this.multiProvider.tryGetDomainId(domain); - if (domainId === null) { - this.logger.warn( - `Domain ${domain} doesn't have chain metadata provided, skipping ...`, - ); - return false; - } - - availableDomainIds.push(domainId); - return true; - }, - ); - - return { availableDomains, availableDomainIds }; } } diff --git a/typescript/sdk/src/ism/HyperlaneIsmFactory.hardhat-test.ts b/typescript/sdk/src/ism/HyperlaneIsmFactory.hardhat-test.ts index 5e7b004a04..894eaa2781 100644 --- a/typescript/sdk/src/ism/HyperlaneIsmFactory.hardhat-test.ts +++ b/typescript/sdk/src/ism/HyperlaneIsmFactory.hardhat-test.ts @@ -168,7 +168,6 @@ describe('HyperlaneIsmFactory', async () => { ismFactoryDeployer = new HyperlaneProxyFactoryDeployer(multiProvider); ismFactory = new HyperlaneIsmFactory(contractsMap, multiProvider); - ismFactory.setDeployer(new TestCoreDeployer(multiProvider, ismFactory)); exampleRoutingConfig = { type: IsmType.ROUTING, diff --git a/typescript/sdk/src/ism/HyperlaneIsmFactory.ts b/typescript/sdk/src/ism/HyperlaneIsmFactory.ts index c2f42b8dbf..645fee8aae 100644 --- a/typescript/sdk/src/ism/HyperlaneIsmFactory.ts +++ b/typescript/sdk/src/ism/HyperlaneIsmFactory.ts @@ -2,6 +2,7 @@ import { ethers } from 'ethers'; import { Logger } from 'pino'; import { + ArbL2ToL1Ism__factory, DefaultFallbackRoutingIsm, DefaultFallbackRoutingIsm__factory, DomainRoutingIsm, @@ -33,7 +34,10 @@ import { import { HyperlaneApp } from '../app/HyperlaneApp.js'; import { appFromAddressesMapHelper } from '../contracts/contracts.js'; -import { HyperlaneAddressesMap } from '../contracts/types.js'; +import { + HyperlaneAddressesMap, + HyperlaneContractsMap, +} from '../contracts/types.js'; import { HyperlaneDeployer } from '../deploy/HyperlaneDeployer.js'; import { ProxyFactoryFactories, @@ -55,15 +59,39 @@ import { } from './types.js'; import { routingModuleDelta } from './utils.js'; +const ismFactories = { + [IsmType.PAUSABLE]: new PausableIsm__factory(), + [IsmType.TRUSTED_RELAYER]: new TrustedRelayerIsm__factory(), + [IsmType.TEST_ISM]: new TestIsm__factory(), + [IsmType.OP_STACK]: new OPStackIsm__factory(), + [IsmType.ARB_L2_TO_L1]: new ArbL2ToL1Ism__factory(), +}; + +class IsmDeployer extends HyperlaneDeployer<{}, typeof ismFactories> { + protected readonly cachingEnabled = false; + + deployContracts(_chain: ChainName, _config: any): Promise { + throw new Error('Method not implemented.'); + } +} + export class HyperlaneIsmFactory extends HyperlaneApp { // The shape of this object is `ChainMap
`, // although `any` is use here because that type breaks a lot of signatures. // TODO: fix this in the next refactoring public deployedIsms: ChainMap = {}; + protected readonly deployer: IsmDeployer; - protected deployer?: HyperlaneDeployer; - setDeployer(deployer: HyperlaneDeployer): void { - this.deployer = deployer; + constructor( + contractsMap: HyperlaneContractsMap, + public readonly multiProvider: MultiProvider, + ) { + super( + contractsMap, + multiProvider, + rootLogger.child({ module: 'ismFactoryApp' }), + ); + this.deployer = new IsmDeployer(multiProvider, ismFactories); } static fromAddressesMap( @@ -75,11 +103,7 @@ export class HyperlaneIsmFactory extends HyperlaneApp { proxyFactoryFactories, multiProvider, ); - return new HyperlaneIsmFactory( - helper.contractsMap, - multiProvider, - rootLogger.child({ module: 'ismFactoryApp' }), - ); + return new HyperlaneIsmFactory(helper.contractsMap, multiProvider); } async deploy(params: { @@ -142,56 +166,39 @@ export class HyperlaneIsmFactory extends HyperlaneApp { }); break; case IsmType.OP_STACK: - assert( - this.deployer, - `HyperlaneDeployer must be set to deploy ${ismType}`, - ); - contract = await this.deployer.deployContractFromFactory( - destination, - new OPStackIsm__factory(), - IsmType.OP_STACK, - [config.nativeBridge], - ); + contract = await this.deployer.deployContract(destination, ismType, [ + config.nativeBridge, + ]); break; case IsmType.PAUSABLE: - assert( - this.deployer, - `HyperlaneDeployer must be set to deploy ${ismType}`, - ); - contract = await this.deployer.deployContractFromFactory( + contract = await this.deployer.deployContract( destination, - new PausableIsm__factory(), IsmType.PAUSABLE, [config.owner], ); - await this.deployer.transferOwnershipOfContracts(destination, config, { - [IsmType.PAUSABLE]: contract, - }); break; case IsmType.TRUSTED_RELAYER: - assert( - this.deployer, - `HyperlaneDeployer must be set to deploy ${ismType}`, - ); assert(mailbox, `Mailbox address is required for deploying ${ismType}`); - contract = await this.deployer.deployContractFromFactory( + contract = await this.deployer.deployContract( destination, - new TrustedRelayerIsm__factory(), IsmType.TRUSTED_RELAYER, [mailbox, config.relayer], ); break; case IsmType.TEST_ISM: - if (!this.deployer) { - throw new Error(`HyperlaneDeployer must be set to deploy ${ismType}`); - } - contract = await this.deployer.deployContractFromFactory( + contract = await this.deployer.deployContract( destination, - new TestIsm__factory(), IsmType.TEST_ISM, [], ); break; + case IsmType.ARB_L2_TO_L1: + contract = await this.deployer.deployContract( + destination, + IsmType.ARB_L2_TO_L1, + [config.bridge], + ); + break; default: throw new Error(`Unsupported ISM type ${ismType}`); } diff --git a/typescript/sdk/src/token/EvmERC20WarpModule.ts b/typescript/sdk/src/token/EvmERC20WarpModule.ts index 19ea854899..6dec4b093b 100644 --- a/typescript/sdk/src/token/EvmERC20WarpModule.ts +++ b/typescript/sdk/src/token/EvmERC20WarpModule.ts @@ -15,11 +15,11 @@ import { rootLogger, } from '@hyperlane-xyz/utils'; +import { transferOwnershipTransactions } from '../contracts/contracts.js'; import { HyperlaneModule, HyperlaneModuleParams, } from '../core/AbstractHyperlaneModule.js'; -import { EvmModuleDeployer } from '../deploy/EvmModuleDeployer.js'; import { EvmIsmModule } from '../ism/EvmIsmModule.js'; import { DerivedIsmConfig } from '../ism/EvmIsmReader.js'; import { MultiProvider } from '../providers/MultiProvider.js'; @@ -215,12 +215,13 @@ export class EvmERC20WarpModule extends HyperlaneModule< actualConfig: TokenRouterConfig, expectedConfig: TokenRouterConfig, ): AnnotatedEV5Transaction[] { - return EvmModuleDeployer.createTransferOwnershipTx({ - actualOwner: actualConfig.owner, - expectedOwner: expectedConfig.owner, - deployedAddress: this.args.addresses.deployedTokenRoute, - chainId: this.domainId, - }); + return transferOwnershipTransactions( + this.multiProvider.getDomainId(this.args.chain), + this.args.addresses.deployedTokenRoute, + actualConfig, + expectedConfig, + `${expectedConfig.type} Warp Route`, + ); } /** diff --git a/typescript/utils/src/index.ts b/typescript/utils/src/index.ts index 0c82c6782a..26921fa1c6 100644 --- a/typescript/utils/src/index.ts +++ b/typescript/utils/src/index.ts @@ -103,10 +103,12 @@ export { parseLegacyMultisigIsmMetadata, } from './multisig.js'; export { + ObjectDiff, ValueOf, arrayToObject, deepCopy, deepEquals, + diffObjMerge, invertKeysAndValues, isObjEmpty, isObject, @@ -120,11 +122,14 @@ export { pick, promiseObjAll, stringifyObject, - diffObjMerge, - ObjectDiff, } from './objects.js'; export { Result, failure, success } from './result.js'; -export { difference, setEquality, symmetricDifference } from './sets.js'; +export { + difference, + intersection, + setEquality, + symmetricDifference, +} from './sets.js'; export { errorToString, fromHexString, diff --git a/typescript/utils/src/sets.ts b/typescript/utils/src/sets.ts index 18149ae375..587c00a626 100644 --- a/typescript/utils/src/sets.ts +++ b/typescript/utils/src/sets.ts @@ -22,3 +22,13 @@ export function symmetricDifference(a: Set, b: Set) { export function setEquality(a: Set, b: Set) { return symmetricDifference(a, b).size === 0; } + +export function intersection(a: Set, b: Set) { + const _intersection = new Set(); + a.forEach((elem) => { + if (b.has(elem)) { + _intersection.add(elem); + } + }); + return _intersection; +} From 39a9b20388ca5ede1805bac460eee2e33ddbb513 Mon Sep 17 00:00:00 2001 From: Tien Dao <15717476+tiendn@users.noreply.github.com> Date: Tue, 29 Oct 2024 00:56:51 +0700 Subject: [PATCH 33/61] test: add more object utils test + remove undefined value when use with invertKeysAndValues fnc (#4764) ### Description Add filter `undefined/null` value when use with utils **invertKeysAndValues** function Add more test at `objects.test.ts` ### Drive-by changes ```diff return Object.fromEntries( Object.entries(data) + .filter(([_, value]) => value !== undefined && value !== null) // Filter out undefined and null values .map(([key, value]) => [value, key]), ); ``` ### Related issues None ### Backward compatibility Currently, nowhere call this **invertKeysAndValues** func ### Testing Yes, more tests. --------- Co-authored-by: J M Rossy --- .changeset/tidy-meals-add.md | 5 ++ typescript/utils/src/objects.test.ts | 130 +++++++++++++++++++++++++++ typescript/utils/src/objects.ts | 4 +- 3 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 .changeset/tidy-meals-add.md diff --git a/.changeset/tidy-meals-add.md b/.changeset/tidy-meals-add.md new file mode 100644 index 0000000000..47b2fee5ad --- /dev/null +++ b/.changeset/tidy-meals-add.md @@ -0,0 +1,5 @@ +--- +"@hyperlane-xyz/utils": patch +--- + +Filter undefined/null values in invertKeysAndValues function diff --git a/typescript/utils/src/objects.test.ts b/typescript/utils/src/objects.test.ts index beab06bc38..3318c682eb 100644 --- a/typescript/utils/src/objects.test.ts +++ b/typescript/utils/src/objects.test.ts @@ -1,12 +1,24 @@ import { expect } from 'chai'; import { + arrayToObject, deepCopy, deepEquals, + deepFind, diffObjMerge, + invertKeysAndValues, + isObjEmpty, isObject, + objFilter, + objKeys, + objLength, + objMap, + objMapEntries, objMerge, objOmit, + pick, + promiseObjAll, + stringifyObject, } from './objects.js'; describe('Object utilities', () => { @@ -83,6 +95,124 @@ describe('Object utilities', () => { expect(isObject(42)).to.be.false; }); + it('objKeys', () => { + const obj = { a: 1, b: 2 }; + expect(objKeys(obj)).to.eql(['a', 'b']); + }); + + it('objLength', () => { + const obj = { a: 1, b: 2 }; + expect(objLength(obj)).to.equal(2); + }); + + it('isObjEmpty', () => { + expect(isObjEmpty({})).to.be.true; + expect(isObjEmpty({ a: 1 })).to.be.false; + }); + + it('objMapEntries', () => { + const obj = { a: 1, b: 2 }; + const result = objMapEntries(obj, (k, v) => v * 2); + expect(result).to.eql([ + ['a', 2], + ['b', 4], + ]); + }); + + it('objMap', () => { + const obj = { a: 1, b: 2 }; + const result = objMap(obj, (k, v) => v * 2); + expect(result).to.eql({ a: 2, b: 4 }); + }); + + it('objFilter', () => { + const obj = { a: 1, b: 2, c: 3 }; + const result = objFilter(obj, (k: string, v: number): v is number => v > 1); + expect(result).to.eql({ b: 2, c: 3 }); + }); + + it('deepFind should find nested object', () => { + const obj = { a: { b: { c: 3 } } }; + const result = deepFind( + obj, + (v: any): v is { c: number } => v && v.c === 3, + ); + expect(result).to.eql({ c: 3 }); + }); + + it('deepFind should return undefined if object is not found', () => { + const obj = { a: { b: { c: 3 } } }; + const result = deepFind( + obj, + (v: any): v is { c: number } => v && v.c === 4, + ); + expect(result).to.be.undefined; + }); + + it('promiseObjAll', async () => { + const obj = { a: Promise.resolve(1), b: Promise.resolve(2) }; + const result = await promiseObjAll(obj); + expect(result).to.eql({ a: 1, b: 2 }); + }); + + it('pick should return a subset of the object', () => { + const obj = { a: 1, b: 2, c: 3 }; + const result = pick(obj, ['a', 'c']); + expect(result).to.eql({ a: 1, c: 3 }); + }); + + it('pick should return an empty object if no keys are provided', () => { + const obj = { a: 1, b: 2, c: 3 }; + const result = pick(obj, []); + expect(result).to.eql({}); + }); + + it("pick should return an empty object if the object doesn't contain the keys", () => { + const obj = { c: 4, d: 5 }; + const result = pick(obj as any, ['a', 'b']); + expect(result).to.eql({}); + }); + + describe('invertKeysAndValues', () => { + it('invertKeysAndValues should invert the keys and values', () => { + const obj = { a: '1', b: '2' }; + const result = invertKeysAndValues(obj); + expect(result).to.eql({ '1': 'a', '2': 'b' }); + }); + + it('invertKeysAndValues should return an empty object if the object is empty', () => { + const obj = {}; + const result = invertKeysAndValues(obj); + expect(result).to.eql({}); + }); + + it('invertKeysAndValues should return an object if the object has duplicate values', () => { + const obj = { a: '1', b: '1' }; + const result = invertKeysAndValues(obj); + expect(result).to.eql({ '1': 'b' }); + }); + + it('invertKeysAndValues should return an object if the object has undefined/null values', () => { + const obj = { a: '1', b: '2', c: undefined, d: null, e: 0 }; + const result = invertKeysAndValues(obj); + expect(result).to.eql({ '1': 'a', '2': 'b', '0': 'e' }); + }); + }); + + it('arrayToObject', () => { + const keys = ['a', 'b']; + const result = arrayToObject(keys); + expect(result).to.eql({ a: true, b: true }); + }); + + it('stringifyObject', () => { + const obj = { a: 1, b: 2 }; + const jsonResult = stringifyObject(obj, 'json'); + expect(jsonResult).to.equal('{"a":1,"b":2}'); + const yamlResult = stringifyObject(obj, 'yaml'); + expect(yamlResult).to.include('a: 1\nb: 2'); + }); + describe('diffObjMerge', () => { it('should merge objects with equal values', () => { const actual = { a: 1, b: 2 }; diff --git a/typescript/utils/src/objects.ts b/typescript/utils/src/objects.ts index 1917cbff69..0c021587f4 100644 --- a/typescript/utils/src/objects.ts +++ b/typescript/utils/src/objects.ts @@ -192,7 +192,9 @@ export function objOmit = any>( export function invertKeysAndValues(data: any) { return Object.fromEntries( - Object.entries(data).map(([key, value]) => [value, key]), + Object.entries(data) + .filter(([_, value]) => value !== undefined && value !== null) // Filter out undefined and null values + .map(([key, value]) => [value, key]), ); } From a028e1624380e4011c71078a4bb704604cda1b84 Mon Sep 17 00:00:00 2001 From: Paul Balaji <10051819+paulbalaji@users.noreply.github.com> Date: Mon, 28 Oct 2024 18:04:54 +0000 Subject: [PATCH 34/61] chore: migrate away from unmaintained rust toolchain installer (#4465) chore: migrate away from unmaintained rust toolchain installer - previous toolchain action was archived almost a year ago https://github.com/actions-rs/toolchain - new action has 1k+ stars and sensible defaults https://github.com/dtolnay/rust-toolchain - uses minimal profile by default - `stable` toolchain by default Signed-off-by: pbio <10051819+paulbalaji@users.noreply.github.com> --- .github/workflows/agent-release-artifacts.yml | 4 +--- .github/workflows/rust.yml | 9 ++------- .github/workflows/test.yml | 5 +---- 3 files changed, 4 insertions(+), 14 deletions(-) diff --git a/.github/workflows/agent-release-artifacts.yml b/.github/workflows/agent-release-artifacts.yml index b2a190f7e1..ee27483c82 100644 --- a/.github/workflows/agent-release-artifacts.yml +++ b/.github/workflows/agent-release-artifacts.yml @@ -58,10 +58,8 @@ jobs: linker = "aarch64-linux-gnu-gcc" EOF - name: setup rust - uses: actions-rs/toolchain@v1 + uses: dtolnay/rust-toolchain@stable with: - toolchain: stable - profile: minimal target: ${{ matrix.TARGET }} - name: setup target run: rustup target add ${{ matrix.TARGET }} diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 18b856bc4b..c0431b4e98 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -35,10 +35,7 @@ jobs: - uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.sha || github.sha }} - - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - profile: minimal + - uses: dtolnay/rust-toolchain@stable - name: rust cache uses: Swatinem/rust-cache@v2 with: @@ -68,10 +65,8 @@ jobs: - uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.sha || github.sha }} - - uses: actions-rs/toolchain@v1 + - uses: dtolnay/rust-toolchain@stable with: - toolchain: stable - profile: minimal components: rustfmt, clippy target: wasm32-unknown-unknown - name: rust cache diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index bfbb3c8f6d..92d203446e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -187,10 +187,7 @@ jobs: uses: foundry-rs/foundry-toolchain@v1 - name: setup rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - profile: minimal + uses: dtolnay/rust-toolchain@stable - name: rust cache uses: Swatinem/rust-cache@v2 From 956ff752ab89ca56718240c2b332a7ae4d5b1259 Mon Sep 17 00:00:00 2001 From: Paul Balaji <10051819+paulbalaji@users.noreply.github.com> Date: Mon, 28 Oct 2024 18:27:18 +0000 Subject: [PATCH 35/61] feat: ergonomic IGP configuration in CLI (#4635) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Description Re-adding the ability to generate IGP hook configs using the CLI, but repurposing logic found in infra to make the configuration experience more ergonomic. Logic still behind the `--advanced` flag. > Enabling this allows IGP configuration in any place that supports hook config e.g. `core`/`warp`/`hook` init with `--advanced`. We will use metadata in registry to: 1. fetch price from Coingecko (prompt user if unable to find) 1. fetch current gas prices via the default RPCs 1. request user to enter an IGP margin in % 1. Calculate the `gasPrice` + `tokenExchangeRate` for you Note that it still sets `overhead` to some preexisting default. ```sh ? Select hook type interchainGasPaymaster Creating interchainGasPaymaster... ? Detected owner address as 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 from signer, is this correct? yes ? Use this same address for the beneficiary? yes ? Select network type Mainnet ? Select local chain for IGP hook bitlayer ? Select remote destination chains for IGP hook alephzero, ancient8 ? Enter overhead for alephzero (e.g., 75000) for IGP hook 75000 ? Enter overhead for ancient8 (e.g., 75000) for IGP hook 75000 Getting gas token prices for all chains from Coingecko... Gas price for alephzero is 40.0 Gas token price for alephzero is $0.393347 Gas price for ancient8 is 0.001000252 Gas token price for ancient8 is $2356.71 Gas price for bitlayer is 0.050000007 Gas token price for bitlayer is $60576 ? Enter IGP margin percentage (e.g. 10 for 10%) 100 Created interchainGasPaymaster! ``` ```sh Core config is valid, writing to file ./configs/core-config.yaml: owner: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" defaultIsm: type: trustedRelayerIsm relayer: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" defaultHook: type: aggregationHook hooks: - type: merkleTreeHook - owner: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" type: interchainGasPaymaster beneficiary: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" oracleKey: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" overhead: alephzero: 75000 ancient8: 75000 oracleConfig: alephzero: gasPrice: "40000000000" tokenExchangeRate: "129868" ancient8: gasPrice: "1000253" tokenExchangeRate: "778100236" requiredHook: owner: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" type: protocolFee beneficiary: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" maxProtocolFee: "1000000000000000000" protocolFee: "0" ✅ Successfully created new core deployment config. ``` ### Drive-by changes Moving reusable infra logic into the SDK, and refactoring CLI+Infra to reuse the underlying logic. For example: - fetching token prices from coingecko - fetching gas prices using a chain's RPC ### Related issues Most recently, https://github.com/hyperlane-xyz/hyperlane-registry/pull/236#issuecomment-2383956859. But there have been numerous occasions where it would be nice for users to be self-sufficient in configuring and deploying an IGP hook for their PI deployments/relayer. ### Backward compatibility yes ### Testing - creating igp config with `hyperlane core init --advanced` - making sure infra print-token-prices.ts still works - making sure infra print-gas-prices.ts still works --- .changeset/sixty-eggs-smoke.md | 5 + .changeset/thin-tips-explain.md | 5 + typescript/cli/src/config/hooks.ts | 231 ++++++++++++---- typescript/cli/src/utils/chains.ts | 12 +- .../infra/config/environments/mainnet3/igp.ts | 22 +- .../config/environments/test/gas-oracle.ts | 2 +- .../infra/config/environments/testnet4/igp.ts | 21 +- .../scripts/agents/update-agent-config.ts | 4 +- typescript/infra/scripts/print-gas-prices.ts | 14 +- typescript/infra/src/config/gas-oracle.ts | 115 +------- typescript/sdk/src/gas/utils.ts | 251 ++++++++++++++++++ typescript/sdk/src/index.ts | 10 + 12 files changed, 516 insertions(+), 176 deletions(-) create mode 100644 .changeset/sixty-eggs-smoke.md create mode 100644 .changeset/thin-tips-explain.md create mode 100644 typescript/sdk/src/gas/utils.ts diff --git a/.changeset/sixty-eggs-smoke.md b/.changeset/sixty-eggs-smoke.md new file mode 100644 index 0000000000..24906d7c69 --- /dev/null +++ b/.changeset/sixty-eggs-smoke.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/cli': minor +--- + +Enable configuration of IGP hooks in the CLI diff --git a/.changeset/thin-tips-explain.md b/.changeset/thin-tips-explain.md new file mode 100644 index 0000000000..330e57a8a3 --- /dev/null +++ b/.changeset/thin-tips-explain.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/sdk': minor +--- + +Introduce utils that can be reused by the CLI and Infra for fetching token prices from Coingecko and gas prices from EVM/Cosmos chains. diff --git a/typescript/cli/src/config/hooks.ts b/typescript/cli/src/config/hooks.ts index a075d66550..0bfd8cb1f6 100644 --- a/typescript/cli/src/config/hooks.ts +++ b/typescript/cli/src/config/hooks.ts @@ -4,22 +4,33 @@ import { ethers } from 'ethers'; import { z } from 'zod'; import { + ChainGasOracleParams, ChainMap, + ChainMetadata, ChainName, HookConfig, HookConfigSchema, HookType, + IgpHookConfig, + MultiProtocolProvider, + getCoingeckoTokenPrices, + getGasPrice, + getLocalStorageGasOracleConfig, } from '@hyperlane-xyz/sdk'; import { Address, normalizeAddressEvm, + objFilter, objMap, toWei, } from '@hyperlane-xyz/utils'; import { CommandContext } from '../context/types.js'; import { errorRed, logBlue, logGreen, logRed } from '../logger.js'; -import { runMultiChainSelectionStep } from '../utils/chains.js'; +import { + runMultiChainSelectionStep, + runSingleChainSelectionStep, +} from '../utils/chains.js'; import { readYamlOrJson } from '../utils/files.js'; import { detectAndConfirmOrPrompt, inputWithInfo } from '../utils/input.js'; @@ -96,6 +107,11 @@ export async function createHookConfig({ name: HookType.PROTOCOL_FEE, description: 'Charge fees for each message dispatch from this chain', }, + { + value: HookType.INTERCHAIN_GAS_PAYMASTER, + name: HookType.INTERCHAIN_GAS_PAYMASTER, + description: 'Pay for gas on remote chains', + }, ], pageSize: 10, }); @@ -107,6 +123,8 @@ export async function createHookConfig({ return createMerkleTreeConfig(); case HookType.PROTOCOL_FEE: return createProtocolFeeConfig(context, advanced); + case HookType.INTERCHAIN_GAS_PAYMASTER: + return createIGPConfig(context, advanced); default: throw new Error(`Invalid hook type: ${hookType}`); } @@ -124,30 +142,13 @@ export const createProtocolFeeConfig = callWithConfigCreationLogs( context: CommandContext, advanced: boolean = false, ): Promise => { - const unnormalizedOwner = - !advanced && context.signer - ? await context.signer.getAddress() - : await detectAndConfirmOrPrompt( - async () => context.signer?.getAddress(), - 'For protocol fee hook, enter', - 'owner address', - 'signer', - ); - const owner = normalizeAddressEvm(unnormalizedOwner); - let beneficiary = owner; - - const isBeneficiarySameAsOwner = advanced - ? await confirm({ - message: `Use this same address (${owner}) for the beneficiary?`, - }) - : true; - - if (!isBeneficiarySameAsOwner) { - const unnormalizedBeneficiary = await input({ - message: 'Enter beneficiary address for protocol fee hook:', - }); - beneficiary = normalizeAddressEvm(unnormalizedBeneficiary); - } + // Get owner and beneficiary + const { owner, beneficiary } = await getOwnerAndBeneficiary( + 'Protocol Fee Hook', + context, + advanced, + ); + // TODO: input in gwei, wei, etc const maxProtocolFee = advanced ? toWei( @@ -182,51 +183,167 @@ export const createProtocolFeeConfig = callWithConfigCreationLogs( HookType.PROTOCOL_FEE, ); -// TODO: make this usable export const createIGPConfig = callWithConfigCreationLogs( - async (remotes: ChainName[]): Promise => { - const unnormalizedOwner = await input({ - message: 'Enter owner address for IGP hook', - }); - const owner = normalizeAddressEvm(unnormalizedOwner); - let beneficiary = owner; - let oracleKey = owner; + async ( + context: CommandContext, + advanced: boolean = false, + ): Promise => { + // Get owner and beneficiary + const { owner, beneficiary } = await getOwnerAndBeneficiary( + 'Interchain Gas Paymaster', + context, + advanced, + ); + + // Determine local and remote chains + const { localChain, remoteChains } = await selectIgpChains(context); + + // Get overhead, defaulting to 75000 + const overhead = await getIgpOverheads(remoteChains); + + // Only get prices for local and remote chains + const filteredMetadata = objFilter( + context.chainMetadata, + (_, metadata): metadata is ChainMetadata => + remoteChains.includes(metadata.name) || metadata.name === localChain, + ); + const prices = await getIgpTokenPrices(context, filteredMetadata); + + // Get exchange rate margin percentage, defaulting to 10 + const exchangeRateMarginPct = parseInt( + await input({ + message: `Enter IGP margin percentage (e.g. 10 for 10%)`, + default: '10', + }), + 10, + ); - const beneficiarySameAsOwner = await confirm({ - message: 'Use this same address for the beneficiary and gasOracleKey?', + // Calculate storage gas oracle config + const oracleConfig = getLocalStorageGasOracleConfig({ + local: localChain, + gasOracleParams: prices, + exchangeRateMarginPct, }); - if (!beneficiarySameAsOwner) { - const unnormalizedBeneficiary = await input({ - message: 'Enter beneficiary address for IGP hook', - }); - beneficiary = normalizeAddressEvm(unnormalizedBeneficiary); - const unnormalizedOracleKey = await input({ - message: 'Enter gasOracleKey address for IGP hook', - }); - oracleKey = normalizeAddressEvm(unnormalizedOracleKey); - } - const overheads: ChainMap = {}; - for (const chain of remotes) { - const overhead = parseInt( - await input({ - message: `Enter overhead for ${chain} (eg 75000) for IGP hook`, - }), - ); - overheads[chain] = overhead; - } return { type: HookType.INTERCHAIN_GAS_PAYMASTER, beneficiary, owner, - oracleKey, - overhead: overheads, - oracleConfig: {}, + oracleKey: owner, + overhead, + oracleConfig, }; }, HookType.INTERCHAIN_GAS_PAYMASTER, ); +async function getOwnerAndBeneficiary( + module: string, + context: CommandContext, + advanced: boolean, +) { + const unnormalizedOwner = + !advanced && context.signer + ? await context.signer.getAddress() + : await detectAndConfirmOrPrompt( + async () => context.signer?.getAddress(), + `For ${module}, enter`, + 'owner address', + 'signer', + ); + const owner = normalizeAddressEvm(unnormalizedOwner); + + let beneficiary = owner; + const beneficiarySameAsOwner = await confirm({ + message: `Use this same address (${owner}) for the beneficiary?`, + }); + if (!beneficiarySameAsOwner) { + const unnormalizedBeneficiary = await input({ + message: `Enter beneficiary address for ${module}`, + }); + beneficiary = normalizeAddressEvm(unnormalizedBeneficiary); + } + + return { owner, beneficiary }; +} + +async function selectIgpChains(context: CommandContext) { + const localChain = await runSingleChainSelectionStep( + context.chainMetadata, + 'Select local chain for IGP hook', + ); + const isTestnet = context.chainMetadata[localChain].isTestnet; + const remoteChains = await runMultiChainSelectionStep({ + chainMetadata: objFilter( + context.chainMetadata, + (_, metadata): metadata is ChainMetadata => metadata.name !== localChain, + ), + message: 'Select remote destination chains for IGP hook', + requireNumber: 1, + networkType: isTestnet ? 'testnet' : 'mainnet', + }); + + return { localChain, remoteChains }; +} + +async function getIgpOverheads(remoteChains: ChainName[]) { + const overhead: ChainMap = {}; + for (const chain of remoteChains) { + overhead[chain] = parseInt( + await input({ + message: `Enter overhead for ${chain} (e.g., 75000) for IGP hook`, + default: '75000', + }), + ); + } + return overhead; +} + +async function getIgpTokenPrices( + context: CommandContext, + filteredMetadata: ChainMap, +) { + const isTestnet = + context.chainMetadata[Object.keys(filteredMetadata)[0]].isTestnet; + const fetchedPrices = isTestnet + ? objMap(filteredMetadata, () => '10') + : await getCoingeckoTokenPrices(filteredMetadata); + + logBlue( + isTestnet + ? `Hardcoding all gas token prices to 10 USD for testnet...` + : `Getting gas token prices for all chains from Coingecko...`, + ); + + const mpp = new MultiProtocolProvider(context.chainMetadata); + const prices: ChainMap = {}; + + for (const chain of Object.keys(filteredMetadata)) { + const gasPrice = await getGasPrice(mpp, chain); + logBlue(`Gas price for ${chain} is ${gasPrice.amount}`); + + let tokenPrice = fetchedPrices[chain]; + if (!tokenPrice) { + tokenPrice = await input({ + message: `Enter the price of ${chain}'s token in USD`, + }); + } else { + logBlue(`Gas token price for ${chain} is $${tokenPrice}`); + } + + const decimals = context.chainMetadata[chain].nativeToken?.decimals; + if (!decimals) { + throw new Error(`No decimals found in metadata for ${chain}`); + } + prices[chain] = { + gasPrice, + nativeToken: { price: tokenPrice, decimals }, + }; + } + + return prices; +} + export const createAggregationConfig = callWithConfigCreationLogs( async ( context: CommandContext, diff --git a/typescript/cli/src/utils/chains.ts b/typescript/cli/src/utils/chains.ts index 09975aca36..f5fb2b3414 100644 --- a/typescript/cli/src/utils/chains.ts +++ b/typescript/cli/src/utils/chains.ts @@ -58,6 +58,13 @@ type RunMultiChainSelectionStepOptions = { * @default false */ requiresConfirmation?: boolean; + + /** + * The network type to filter the chains by + * + * @default undefined + */ + networkType?: 'mainnet' | 'testnet'; }; export async function runMultiChainSelectionStep({ @@ -65,11 +72,12 @@ export async function runMultiChainSelectionStep({ message = 'Select chains', requireNumber = 0, requiresConfirmation = false, + networkType = undefined, }: RunMultiChainSelectionStepOptions) { - const networkType = await selectNetworkType(); + const selectedNetworkType = networkType ?? (await selectNetworkType()); const { choices, networkTypeSeparator } = getChainChoices( chainMetadata, - networkType, + selectedNetworkType, ); let currentChoiceSelection = new Set(); diff --git a/typescript/infra/config/environments/mainnet3/igp.ts b/typescript/infra/config/environments/mainnet3/igp.ts index 6b0d8fbbc2..fa4ab8e919 100644 --- a/typescript/infra/config/environments/mainnet3/igp.ts +++ b/typescript/infra/config/environments/mainnet3/igp.ts @@ -1,12 +1,19 @@ -import { ChainMap, ChainName, HookType, IgpConfig } from '@hyperlane-xyz/sdk'; +import { + ChainMap, + ChainName, + HookType, + IgpConfig, + getTokenExchangeRateFromValues, +} from '@hyperlane-xyz/sdk'; import { exclude, objMap } from '@hyperlane-xyz/utils'; import { AllStorageGasOracleConfigs, + EXCHANGE_RATE_MARGIN_PCT, getAllStorageGasOracleConfigs, getOverhead, - getTokenExchangeRateFromValues, } from '../../../src/config/gas-oracle.js'; +import { mustGetChainNativeToken } from '../../../src/utils/utils.js'; import { ethereumChainNames } from './chains.js'; import gasPrices from './gasPrices.json'; @@ -29,7 +36,16 @@ const storageGasOracleConfig: AllStorageGasOracleConfigs = supportedChainNames, gasPrices, (local, remote) => - getTokenExchangeRateFromValues(local, remote, tokenPrices), + getTokenExchangeRateFromValues({ + local, + remote, + tokenPrices, + exchangeRateMarginPct: EXCHANGE_RATE_MARGIN_PCT, + decimals: { + local: mustGetChainNativeToken(local).decimals, + remote: mustGetChainNativeToken(remote).decimals, + }, + }), (local) => parseFloat(tokenPrices[local]), (local, remote) => getOverheadWithOverrides(local, remote), ); diff --git a/typescript/infra/config/environments/test/gas-oracle.ts b/typescript/infra/config/environments/test/gas-oracle.ts index cacf8f3a33..65c09d9cdb 100644 --- a/typescript/infra/config/environments/test/gas-oracle.ts +++ b/typescript/infra/config/environments/test/gas-oracle.ts @@ -3,12 +3,12 @@ import { BigNumber, ethers } from 'ethers'; import { ChainMap, ChainName, + GasPriceConfig, TOKEN_EXCHANGE_RATE_DECIMALS, } from '@hyperlane-xyz/sdk'; import { AllStorageGasOracleConfigs, - GasPriceConfig, getAllStorageGasOracleConfigs, } from '../../../src/config/gas-oracle.js'; diff --git a/typescript/infra/config/environments/testnet4/igp.ts b/typescript/infra/config/environments/testnet4/igp.ts index 302d6aeb72..0cabe5b69a 100644 --- a/typescript/infra/config/environments/testnet4/igp.ts +++ b/typescript/infra/config/environments/testnet4/igp.ts @@ -1,12 +1,18 @@ -import { ChainMap, HookType, IgpConfig } from '@hyperlane-xyz/sdk'; +import { + ChainMap, + HookType, + IgpConfig, + getTokenExchangeRateFromValues, +} from '@hyperlane-xyz/sdk'; import { Address, exclude, objMap } from '@hyperlane-xyz/utils'; import { AllStorageGasOracleConfigs, + EXCHANGE_RATE_MARGIN_PCT, getAllStorageGasOracleConfigs, getOverhead, - getTokenExchangeRateFromValues, } from '../../../src/config/gas-oracle.js'; +import { mustGetChainNativeToken } from '../../../src/utils/utils.js'; import { ethereumChainNames } from './chains.js'; import gasPrices from './gasPrices.json'; @@ -21,7 +27,16 @@ export const storageGasOracleConfig: AllStorageGasOracleConfigs = supportedChainNames, gasPrices, (local, remote) => - getTokenExchangeRateFromValues(local, remote, tokenPrices), + getTokenExchangeRateFromValues({ + local, + remote, + tokenPrices, + exchangeRateMarginPct: EXCHANGE_RATE_MARGIN_PCT, + decimals: { + local: mustGetChainNativeToken(local).decimals, + remote: mustGetChainNativeToken(remote).decimals, + }, + }), ); export const igp: ChainMap = objMap( diff --git a/typescript/infra/scripts/agents/update-agent-config.ts b/typescript/infra/scripts/agents/update-agent-config.ts index de01a2cffe..7044bc436e 100644 --- a/typescript/infra/scripts/agents/update-agent-config.ts +++ b/typescript/infra/scripts/agents/update-agent-config.ts @@ -12,6 +12,7 @@ import { HyperlaneDeploymentArtifacts, MultiProvider, buildAgentConfig, + getCosmosChainGasPrice, } from '@hyperlane-xyz/sdk'; import { ProtocolType, @@ -26,7 +27,6 @@ import { DeployEnvironment, envNameToAgentEnv, } from '../../src/config/environment.js'; -import { getCosmosChainGasPrice } from '../../src/config/gas-oracle.js'; import { chainIsProtocol, filterRemoteDomainMetadata, @@ -125,7 +125,7 @@ export async function writeAgentConfig( .map(async (chain) => [ chain, { - gasPrice: await getCosmosChainGasPrice(chain), + gasPrice: await getCosmosChainGasPrice(chain, multiProvider), }, ]), ), diff --git a/typescript/infra/scripts/print-gas-prices.ts b/typescript/infra/scripts/print-gas-prices.ts index bbe15c7b10..39eca69ee2 100644 --- a/typescript/infra/scripts/print-gas-prices.ts +++ b/typescript/infra/scripts/print-gas-prices.ts @@ -1,7 +1,12 @@ import { Provider } from '@ethersproject/providers'; import { ethers } from 'ethers'; -import { ChainMap, MultiProtocolProvider } from '@hyperlane-xyz/sdk'; +import { + ChainMap, + GasPriceConfig, + MultiProtocolProvider, + getCosmosChainGasPrice, +} from '@hyperlane-xyz/sdk'; import { ProtocolType } from '@hyperlane-xyz/utils'; // Intentionally circumvent `mainnet3/index.ts` and `getEnvironmentConfig('mainnet3')` @@ -12,10 +17,6 @@ import { supportedChainNames as mainnet3SupportedChainNames } from '../config/en import { getRegistry as getTestnet4Registry } from '../config/environments/testnet4/chains.js'; import testnet4GasPrices from '../config/environments/testnet4/gasPrices.json' assert { type: 'json' }; import { supportedChainNames as testnet4SupportedChainNames } from '../config/environments/testnet4/supportedChainNames.js'; -import { - GasPriceConfig, - getCosmosChainGasPrice, -} from '../src/config/gas-oracle.js'; import { getArgs } from './agent-utils.js'; @@ -69,8 +70,7 @@ async function getGasPrice( }; } case ProtocolType.Cosmos: { - const { amount } = await getCosmosChainGasPrice(chain); - + const { amount } = await getCosmosChainGasPrice(chain, mpp); return { amount, decimals: 1, diff --git a/typescript/infra/src/config/gas-oracle.ts b/typescript/infra/src/config/gas-oracle.ts index ac3516d103..c040ff45bb 100644 --- a/typescript/infra/src/config/gas-oracle.ts +++ b/typescript/infra/src/config/gas-oracle.ts @@ -2,56 +2,37 @@ import chalk from 'chalk'; import { BigNumber, ethers } from 'ethers'; import { - AgentCosmosGasPrice, ChainMap, ChainName, - StorageGasOracleConfig as DestinationOracleConfig, - TOKEN_EXCHANGE_RATE_DECIMALS, + GasPriceConfig, + StorageGasOracleConfig, TOKEN_EXCHANGE_RATE_SCALE, defaultMultisigConfigs, - getCosmosRegistryChain, multisigIsmVerificationCost, } from '@hyperlane-xyz/sdk'; -import { ProtocolType, convertDecimals } from '@hyperlane-xyz/utils'; -import { getChain } from '../../config/registry.js'; -import { - isEthereumProtocolChain, - mustGetChainNativeToken, -} from '../utils/utils.js'; - -// Gas data to configure on a single local chain. Includes DestinationOracleConfig -// for each remote chain. -export type StorageGasOracleConfig = ChainMap; - -// StorageGasOracleConfigs for each local chain -export type AllStorageGasOracleConfigs = ChainMap; +import { isEthereumProtocolChain } from '../utils/utils.js'; -// A configuration for a gas price. -// Some chains, e.g. Neutron, have gas prices that are -// not integers and and are still quoted in the "wei" version -// of the token. Therefore it's possible for the amount to be a -// float (e.g. "0.0053") and for decimals to be 1. This is why -// we intentionally don't deal with BigNumber here. -export interface GasPriceConfig { - amount: string; - decimals: number; -} +// gas oracle configs for each chain, which includes +// a map for each chain's remote chains +export type AllStorageGasOracleConfigs = ChainMap< + ChainMap +>; // Overcharge by 50% to account for market making risk -const EXCHANGE_RATE_MARGIN_PCT = 50; +export const EXCHANGE_RATE_MARGIN_PCT = 50; -// Gets the StorageGasOracleConfig for a particular local chain. +// Gets the StorageGasOracleConfig for each remote chain for a particular local chain. // Accommodates small non-integer gas prices by scaling up the gas price // and scaling down the exchange rate by the same factor. -function getLocalStorageGasOracleConfig( +function getLocalStorageGasOracleConfigOverride( local: ChainName, remotes: ChainName[], gasPrices: ChainMap, getTokenExchangeRate: (local: ChainName, remote: ChainName) => BigNumber, getTokenUsdPrice?: (chain: ChainName) => number, getOverhead?: (local: ChainName, remote: ChainName) => number, -): StorageGasOracleConfig { +): ChainMap { return remotes.reduce((agg, remote) => { let exchangeRate = getTokenExchangeRate(local, remote); if (!gasPrices[remote]) { @@ -203,7 +184,7 @@ export function getOverhead( : FOREIGN_DEFAULT_OVERHEAD; // non-ethereum overhead } -// Gets the StorageGasOracleConfig for each local chain +// Gets the map of remote gas oracle configs for each local chain export function getAllStorageGasOracleConfigs( chainNames: ChainName[], gasPrices: ChainMap, @@ -215,7 +196,7 @@ export function getAllStorageGasOracleConfigs( const remotes = chainNames.filter((chain) => local !== chain); return { ...agg, - [local]: getLocalStorageGasOracleConfig( + [local]: getLocalStorageGasOracleConfigOverride( local, remotes, gasPrices, @@ -226,71 +207,3 @@ export function getAllStorageGasOracleConfigs( }; }, {}) as AllStorageGasOracleConfigs; } - -// Gets the exchange rate of the remote quoted in local tokens -export function getTokenExchangeRateFromValues( - local: ChainName, - remote: ChainName, - tokenPrices: ChainMap, -): BigNumber { - // Workaround for chicken-egg dependency problem. - // We need to provide some default value here to satisfy the config on initial load, - // whilst knowing that it will get overwritten when a script actually gets run. - // We set default token price to 1 to mitigate underflow/overflow errors that occurred - // on some pairings if the exchange rate itself was set to 1. - const defaultValue = '1'; - const localValue = ethers.utils.parseUnits( - tokenPrices[local] ?? defaultValue, - TOKEN_EXCHANGE_RATE_DECIMALS, - ); - const remoteValue = ethers.utils.parseUnits( - tokenPrices[remote] ?? defaultValue, - TOKEN_EXCHANGE_RATE_DECIMALS, - ); - - // This does not yet account for decimals! - let exchangeRate = remoteValue.mul(TOKEN_EXCHANGE_RATE_SCALE).div(localValue); - // Apply the premium - exchangeRate = exchangeRate.mul(100 + EXCHANGE_RATE_MARGIN_PCT).div(100); - - return BigNumber.from( - convertDecimals( - mustGetChainNativeToken(remote).decimals, - mustGetChainNativeToken(local).decimals, - exchangeRate.toString(), - ), - ); -} - -// Gets the gas price for a Cosmos chain -export async function getCosmosChainGasPrice( - chain: ChainName, -): Promise { - const metadata = getChain(chain); - if (!metadata) { - throw new Error(`No metadata found for Cosmos chain ${chain}`); - } - if (metadata.protocol !== ProtocolType.Cosmos) { - throw new Error(`Chain ${chain} is not a Cosmos chain`); - } - - const cosmosRegistryChain = await getCosmosRegistryChain(chain); - - const nativeToken = mustGetChainNativeToken(chain); - - const fee = cosmosRegistryChain.fees?.fee_tokens.find( - (fee: { denom: string }) => { - return ( - fee.denom === nativeToken.denom || fee.denom === `u${nativeToken.denom}` - ); - }, - ); - if (!fee || fee.average_gas_price === undefined) { - throw new Error(`No gas price found for Cosmos chain ${chain}`); - } - - return { - denom: fee.denom, - amount: fee.average_gas_price.toString(), - }; -} diff --git a/typescript/sdk/src/gas/utils.ts b/typescript/sdk/src/gas/utils.ts new file mode 100644 index 0000000000..3a658d9b6a --- /dev/null +++ b/typescript/sdk/src/gas/utils.ts @@ -0,0 +1,251 @@ +import { Provider } from '@ethersproject/providers'; +import { BigNumber, ethers } from 'ethers'; + +import { ProtocolType, convertDecimals, objMap } from '@hyperlane-xyz/utils'; + +import { + TOKEN_EXCHANGE_RATE_DECIMALS, + TOKEN_EXCHANGE_RATE_SCALE, +} from '../consts/igp.js'; +import { ChainMetadataManager } from '../metadata/ChainMetadataManager.js'; +import { AgentCosmosGasPrice } from '../metadata/agentConfig.js'; +import { ChainMetadata } from '../metadata/chainMetadataTypes.js'; +import { MultiProtocolProvider } from '../providers/MultiProtocolProvider.js'; +import { ChainMap, ChainName } from '../types.js'; +import { getCosmosRegistryChain } from '../utils/cosmos.js'; + +import { StorageGasOracleConfig } from './oracle/types.js'; + +export interface GasPriceConfig { + amount: string; + decimals: number; +} + +export interface NativeTokenPriceConfig { + price: string; + decimals: number; +} + +export interface ChainGasOracleParams { + gasPrice: GasPriceConfig; + nativeToken: NativeTokenPriceConfig; +} + +export async function getGasPrice( + mpp: MultiProtocolProvider, + chain: string, +): Promise { + const protocolType = mpp.getProtocol(chain); + switch (protocolType) { + case ProtocolType.Ethereum: { + const provider = mpp.getProvider(chain); + const gasPrice = await (provider.provider as Provider).getGasPrice(); + return { + amount: ethers.utils.formatUnits(gasPrice, 'gwei'), + decimals: 9, + }; + } + case ProtocolType.Cosmos: { + const { amount } = await getCosmosChainGasPrice(chain, mpp); + return { + amount, + decimals: 1, + }; + } + case ProtocolType.Sealevel: + // TODO get a reasonable value + return { + amount: '0.001', + decimals: 9, + }; + default: + throw new Error(`Unsupported protocol type: ${protocolType}`); + } +} + +// Gets the gas price for a Cosmos chain +export async function getCosmosChainGasPrice( + chain: ChainName, + chainMetadataManager: ChainMetadataManager, +): Promise { + const metadata = chainMetadataManager.getChainMetadata(chain); + if (!metadata) { + throw new Error(`No metadata found for Cosmos chain ${chain}`); + } + if (metadata.protocol !== ProtocolType.Cosmos) { + throw new Error(`Chain ${chain} is not a Cosmos chain`); + } + + const cosmosRegistryChain = await getCosmosRegistryChain(chain); + const nativeToken = metadata.nativeToken; + if (!nativeToken) { + throw new Error(`No native token found for Cosmos chain ${chain}`); + } + if (!nativeToken.denom) { + throw new Error(`No denom found for native token on Cosmos chain ${chain}`); + } + + const fee = cosmosRegistryChain.fees?.fee_tokens.find( + (fee: { denom: string }) => { + return ( + fee.denom === nativeToken.denom || fee.denom === `u${nativeToken.denom}` + ); + }, + ); + if (!fee || fee.average_gas_price === undefined) { + throw new Error(`No gas price found for Cosmos chain ${chain}`); + } + + return { + denom: fee.denom, + amount: fee.average_gas_price.toString(), + }; +} + +// Gets the exchange rate of the remote quoted in local tokens +export function getTokenExchangeRateFromValues({ + local, + remote, + tokenPrices, + exchangeRateMarginPct, + decimals, +}: { + local: ChainName; + remote: ChainName; + tokenPrices: ChainMap; + exchangeRateMarginPct: number; + decimals: { local: number; remote: number }; +}): BigNumber { + // Workaround for chicken-egg dependency problem. + // We need to provide some default value here to satisfy the config on initial load, + // whilst knowing that it will get overwritten when a script actually gets run. + const defaultValue = '1'; + const localValue = ethers.utils.parseUnits( + tokenPrices[local] ?? defaultValue, + TOKEN_EXCHANGE_RATE_DECIMALS, + ); + const remoteValue = ethers.utils.parseUnits( + tokenPrices[remote] ?? defaultValue, + TOKEN_EXCHANGE_RATE_DECIMALS, + ); + + // This does not yet account for decimals! + let exchangeRate = remoteValue.mul(TOKEN_EXCHANGE_RATE_SCALE).div(localValue); + // Apply the premium + exchangeRate = exchangeRate.mul(100 + exchangeRateMarginPct).div(100); + + return BigNumber.from( + convertDecimals(decimals.remote, decimals.local, exchangeRate.toString()), + ); +} + +// Gets the StorageGasOracleConfig for each remote chain for a particular local chain. +// Accommodates small non-integer gas prices by scaling up the gas price +// and scaling down the exchange rate by the same factor. +export function getLocalStorageGasOracleConfig({ + local, + gasOracleParams, + exchangeRateMarginPct, +}: { + local: ChainName; + gasOracleParams: ChainMap; + exchangeRateMarginPct: number; +}): ChainMap { + const remotes = Object.keys(gasOracleParams).filter( + (remote) => remote !== local, + ); + const tokenPrices: ChainMap = objMap( + gasOracleParams, + (chain) => gasOracleParams[chain].nativeToken.price, + ); + const localDecimals = gasOracleParams[local].nativeToken.decimals; + return remotes.reduce((agg, remote) => { + const remoteDecimals = gasOracleParams[remote].nativeToken.decimals; + let exchangeRate = getTokenExchangeRateFromValues({ + local, + remote, + tokenPrices, + exchangeRateMarginPct, + decimals: { local: localDecimals, remote: remoteDecimals }, + }); + + // First parse as a number, so we have floating point precision. + // Recall it's possible to have gas prices that are not integers, even + // after converting to the "wei" version of the token. + let gasPrice = + parseFloat(gasOracleParams[remote].gasPrice.amount) * + Math.pow(10, gasOracleParams[remote].gasPrice.decimals); + if (isNaN(gasPrice)) { + throw new Error( + `Invalid gas price for chain ${remote}: ${gasOracleParams[remote].gasPrice.amount}`, + ); + } + + // We have very little precision and ultimately need an integer value for + // the gas price that will be set on-chain. We scale up the gas price and + // scale down the exchange rate by the same factor. + if (gasPrice < 10 && gasPrice % 1 !== 0) { + // Scale up the gas price by 1e4 + const gasPriceScalingFactor = 1e4; + + // Check that there's no significant underflow when applying + // this to the exchange rate: + const adjustedExchangeRate = exchangeRate.div(gasPriceScalingFactor); + const recoveredExchangeRate = adjustedExchangeRate.mul( + gasPriceScalingFactor, + ); + if (recoveredExchangeRate.mul(100).div(exchangeRate).lt(99)) { + throw new Error('Too much underflow when downscaling exchange rate'); + } + + // Apply the scaling factor + exchangeRate = adjustedExchangeRate; + gasPrice *= gasPriceScalingFactor; + } + + // Our integer gas price. + const gasPriceBn = BigNumber.from(Math.ceil(gasPrice)); + + return { + ...agg, + [remote]: { + tokenExchangeRate: exchangeRate.toString(), + gasPrice: gasPriceBn.toString(), + }, + }; + }, {} as ChainMap); +} + +const COINGECKO_PRICE_API = 'https://api.coingecko.com/api/v3/simple/price'; + +export async function getCoingeckoTokenPrices( + chainMetadata: ChainMap, + currency = 'usd', +): Promise> { + const ids = objMap( + chainMetadata, + (_, metadata) => metadata.gasCurrencyCoinGeckoId ?? metadata.name, + ); + + const resp = await fetch( + `${COINGECKO_PRICE_API}?ids=${Object.entries(ids).join( + ',', + )}&vs_currencies=${currency}`, + ); + + const idPrices = await resp.json(); + + const prices = objMap(ids, (chain, id) => { + const idData = idPrices[id]; + if (!idData) { + return undefined; + } + const price = idData[currency]; + if (!price) { + return undefined; + } + return price.toString(); + }); + + return prices; +} diff --git a/typescript/sdk/src/index.ts b/typescript/sdk/src/index.ts index e9bd31bb42..5ab4630b99 100644 --- a/typescript/sdk/src/index.ts +++ b/typescript/sdk/src/index.ts @@ -539,3 +539,13 @@ export { export { EvmIsmModule } from './ism/EvmIsmModule.js'; export { AnnotatedEV5Transaction } from './providers/ProviderType.js'; export { EvmERC20WarpModule } from './token/EvmERC20WarpModule.js'; +export { + GasPriceConfig, + NativeTokenPriceConfig, + ChainGasOracleParams, + getCoingeckoTokenPrices, + getCosmosChainGasPrice, + getGasPrice, + getLocalStorageGasOracleConfig, + getTokenExchangeRateFromValues, +} from './gas/utils.js'; From f9919c447e02e460595a16ddf3f01518c7b4df21 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Mon, 28 Oct 2024 14:38:15 -0400 Subject: [PATCH 36/61] feat: add lumia warp route to infra (#4705) ### Description - Add lumia warp route to infra config --- .../getEthereumBscLumiaLUMIAWarpConfig.ts | 41 +++++++++++++++++++ .../environments/mainnet3/warp/warpIds.ts | 1 + typescript/infra/config/warp.ts | 2 + 3 files changed, 44 insertions(+) create mode 100644 typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumBscLumiaLUMIAWarpConfig.ts diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumBscLumiaLUMIAWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumBscLumiaLUMIAWarpConfig.ts new file mode 100644 index 0000000000..d063f9a8fb --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumBscLumiaLUMIAWarpConfig.ts @@ -0,0 +1,41 @@ +import { + ChainMap, + RouterConfig, + TokenRouterConfig, + TokenType, +} from '@hyperlane-xyz/sdk'; +import { objMap } from '@hyperlane-xyz/utils'; + +// Lumia Team +const owner = '0x8bBA07Ddc72455b55530C17e6f6223EF6E156863'; + +export const getEthereumBscLUMIAWarpConfig = async ( + routerConfig: ChainMap, +): Promise> => { + const ethereum = { + type: TokenType.collateral, + token: '0xD9343a049D5DBd89CD19DC6BcA8c48fB3a0a42a7', + }; + + const bsc = { + type: TokenType.synthetic, + }; + + const lumia = { + type: TokenType.native, + }; + + const configMap = { + ethereum, + bsc, + lumia, + }; + + const merged = objMap(configMap, (chain, config) => ({ + ...routerConfig[chain], + ...config, + owner, + })); + + return merged as ChainMap; +}; diff --git a/typescript/infra/config/environments/mainnet3/warp/warpIds.ts b/typescript/infra/config/environments/mainnet3/warp/warpIds.ts index a90601c356..35adc72c66 100644 --- a/typescript/infra/config/environments/mainnet3/warp/warpIds.ts +++ b/typescript/infra/config/environments/mainnet3/warp/warpIds.ts @@ -17,6 +17,7 @@ export enum WarpRouteIds { EthereumVictionUSDC = 'USDC/ethereum-viction', EthereumVictionUSDT = 'USDT/ethereum-viction', EthereumZircuitPZETH = 'PZETH/ethereum-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 84086342e7..4e1246ec5f 100644 --- a/typescript/infra/config/warp.ts +++ b/typescript/infra/config/warp.ts @@ -14,6 +14,7 @@ import { getArbitrumNeutronEclipWarpConfig } from './environments/mainnet3/warp/ import { getArbitrumNeutronTiaWarpConfig } from './environments/mainnet3/warp/configGetters/getArbitrumNeutronTiaWarpConfig.js'; import { getEclipseStrideTiaWarpConfig } from './environments/mainnet3/warp/configGetters/getEclipseStrideSTTIAWarpConfig.js'; import { getEclipseStrideStTiaWarpConfig } from './environments/mainnet3/warp/configGetters/getEclipseStrideTIAWarpConfig.js'; +import { getEthereumBscLUMIAWarpConfig } from './environments/mainnet3/warp/configGetters/getEthereumBscLumiaLUMIAWarpConfig.js'; import { getEthereumInevmUSDCWarpConfig } from './environments/mainnet3/warp/configGetters/getEthereumInevmUSDCWarpConfig.js'; import { getEthereumInevmUSDTWarpConfig } from './environments/mainnet3/warp/configGetters/getEthereumInevmUSDTWarpConfig.js'; import { getEthereumSeiFastUSDWarpConfig } from './environments/mainnet3/warp/configGetters/getEthereumSeiFastUSDWarpConfig.js'; @@ -51,6 +52,7 @@ export const warpConfigGetterMap: Record< [WarpRouteIds.EthereumVictionUSDC]: getEthereumVictionUSDCWarpConfig, [WarpRouteIds.EthereumVictionUSDT]: getEthereumVictionUSDTWarpConfig, [WarpRouteIds.EthereumZircuitPZETH]: getRenzoPZETHWarpConfig, + [WarpRouteIds.EthereumBscLumiaLUMIA]: getEthereumBscLUMIAWarpConfig, [WarpRouteIds.MantapacificNeutronTIA]: getMantapacificNeutronTiaWarpConfig, [WarpRouteIds.EclipseStrideTIA]: getEclipseStrideTiaWarpConfig, [WarpRouteIds.EclipseStrideSTTIA]: getEclipseStrideStTiaWarpConfig, From 56328e6e1301c64dc5f483179a173bf5026e8a81 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Mon, 28 Oct 2024 17:07:09 -0400 Subject: [PATCH 37/61] fix: use `ism.route(message)` for supporting self relay of ICA ISM (#4772) ### Description ICA ISM does not implement `module(uint32)` but rather `route(message)`. This modifies ISM derivation with message context to use this function. ### Related issues ### Backward compatibility Yes ### Testing Manual --- .changeset/sweet-houses-type.md | 5 +++++ typescript/sdk/src/ism/EvmIsmReader.ts | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 .changeset/sweet-houses-type.md diff --git a/.changeset/sweet-houses-type.md b/.changeset/sweet-houses-type.md new file mode 100644 index 0000000000..27fa3feeb6 --- /dev/null +++ b/.changeset/sweet-houses-type.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/sdk': patch +--- + +Fix ICA ISM self relay diff --git a/typescript/sdk/src/ism/EvmIsmReader.ts b/typescript/sdk/src/ism/EvmIsmReader.ts index 47e58d2b76..06493cb450 100644 --- a/typescript/sdk/src/ism/EvmIsmReader.ts +++ b/typescript/sdk/src/ism/EvmIsmReader.ts @@ -148,7 +148,9 @@ export class EvmIsmReader extends HyperlaneReader implements IsmReader { ); return; } - const module = await ism.module(domainId); + const module = this.messageContext + ? await ism.route(this.messageContext.message) + : await ism.module(domainId); domains[chainName] = await this.deriveIsmConfig(module); }); From cd3bc5cd37083e121957f9bf9928d51a0f64d2bc Mon Sep 17 00:00:00 2001 From: Paul Balaji <10051819+paulbalaji@users.noreply.github.com> Date: Tue, 29 Oct 2024 10:51:00 +0000 Subject: [PATCH 38/61] chore: disable arcadiatestnet until redeployment (#4775) ### Description chore: disable arcadiatestnet until redeployment - testnet had to be reset ### Drive-by changes ### Related issues ### Backward compatibility ### Testing --------- Signed-off-by: pbio <10051819+paulbalaji@users.noreply.github.com> --- typescript/infra/config/environments/testnet4/agent.ts | 6 +++--- .../environments/testnet4/aw-validators/hyperlane.json | 3 --- typescript/infra/config/environments/testnet4/funding.ts | 2 +- .../config/environments/testnet4/supportedChainNames.ts | 3 ++- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/typescript/infra/config/environments/testnet4/agent.ts b/typescript/infra/config/environments/testnet4/agent.ts index 18b4b7690d..17db9d2ffa 100644 --- a/typescript/infra/config/environments/testnet4/agent.ts +++ b/typescript/infra/config/environments/testnet4/agent.ts @@ -43,7 +43,7 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig< [Role.Validator]: { alfajores: true, arbitrumsepolia: true, - arcadiatestnet: true, + // arcadiatestnet: true, basesepolia: true, berabartio: true, bsctestnet: true, @@ -73,7 +73,7 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig< [Role.Relayer]: { alfajores: true, arbitrumsepolia: true, - arcadiatestnet: true, + // arcadiatestnet: true, basesepolia: true, berabartio: true, bsctestnet: true, @@ -103,7 +103,7 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig< [Role.Scraper]: { alfajores: true, arbitrumsepolia: true, - arcadiatestnet: true, + // arcadiatestnet: true, basesepolia: true, berabartio: true, bsctestnet: true, diff --git a/typescript/infra/config/environments/testnet4/aw-validators/hyperlane.json b/typescript/infra/config/environments/testnet4/aw-validators/hyperlane.json index ce4a9509f9..2bc9d9762b 100644 --- a/typescript/infra/config/environments/testnet4/aw-validators/hyperlane.json +++ b/typescript/infra/config/environments/testnet4/aw-validators/hyperlane.json @@ -9,9 +9,6 @@ "arbitrumsepolia": { "validators": ["0x09fabfbca0b8bf042e2a1161ee5010d147b0f603"] }, - "arcadiatestnet": { - "validators": ["0x7ce5973d3f22971546efb86f5a0417c1248e92f5"] - }, "basesepolia": { "validators": ["0x82e3b437a2944e3ff00258c93e72cd1ba5e0e921"] }, diff --git a/typescript/infra/config/environments/testnet4/funding.ts b/typescript/infra/config/environments/testnet4/funding.ts index 938dd880f5..e022357a97 100644 --- a/typescript/infra/config/environments/testnet4/funding.ts +++ b/typescript/infra/config/environments/testnet4/funding.ts @@ -28,7 +28,7 @@ export const keyFunderConfig: KeyFunderConfig< desiredBalancePerChain: { alfajores: '5', arbitrumsepolia: '0.1', - arcadiatestnet: '0.1', + // arcadiatestnet: '0.1', basesepolia: '0.1', berabartio: '0.1', bsctestnet: '5', diff --git a/typescript/infra/config/environments/testnet4/supportedChainNames.ts b/typescript/infra/config/environments/testnet4/supportedChainNames.ts index 3643ff89a3..7bd9c5b56b 100644 --- a/typescript/infra/config/environments/testnet4/supportedChainNames.ts +++ b/typescript/infra/config/environments/testnet4/supportedChainNames.ts @@ -2,7 +2,8 @@ export const testnet4SupportedChainNames = [ 'alfajores', 'arbitrumsepolia', - 'arcadiatestnet', + // Disabling arcadiatestnet on Oct 29, 2024: chain reset and needs to be redeployed + // 'arcadiatestnet', 'basesepolia', 'berabartio', 'bsctestnet', From 0640f837ce5df887b45a5f476be0c8f4585fa250 Mon Sep 17 00:00:00 2001 From: Kunal Arora <55632507+aroralanuk@users.noreply.github.com> Date: Tue, 29 Oct 2024 16:22:36 +0530 Subject: [PATCH 39/61] fix(contracts): InterchainAccountRouter minor audit remediation (#4581) ### Description - disabled the ICARouter's ability to change hook given that the user doesn't expect the hook to change after they deploy their ICA account. Hook is not part of the derivation like ism on the destination chain and hence, cannot be configured custom by the user. ### Drive-by changes - MailboxClient events for hook and ism setting - ProtocolFee events for setting beneficiary and owner ### Related issues - partly fixes https://github.com/chainlight-io/2024-08-hyperlane/issues/14 ### Backward compatibility No ### Testing Unit tests --- .changeset/silent-berries-attend.md | 5 ++ solidity/contracts/client/MailboxClient.sol | 21 ++++- solidity/contracts/hooks/ProtocolFee.sol | 5 ++ .../middleware/InterchainAccountRouter.sol | 34 ++++++-- .../contracts/test/TestPostDispatchHook.sol | 9 +- solidity/test/InterchainAccountRouter.t.sol | 86 ++++++++++++++++++- solidity/test/hooks/ProtocolFee.t.sol | 10 +++ 7 files changed, 157 insertions(+), 13 deletions(-) create mode 100644 .changeset/silent-berries-attend.md diff --git a/.changeset/silent-berries-attend.md b/.changeset/silent-berries-attend.md new file mode 100644 index 0000000000..2b6b43e5f2 --- /dev/null +++ b/.changeset/silent-berries-attend.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/core': minor +--- + +disabled the ICARouter's ability to change hook given that the user doesn't expect the hook to change after they deploy their ICA account. Hook is not part of the derivation like ism on the destination chain and hence, cannot be configured custom by the user. diff --git a/solidity/contracts/client/MailboxClient.sol b/solidity/contracts/client/MailboxClient.sol index 41ad7f0558..9c986a0cce 100644 --- a/solidity/contracts/client/MailboxClient.sol +++ b/solidity/contracts/client/MailboxClient.sol @@ -1,6 +1,18 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity >=0.6.11; +/*@@@@@@@ @@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@ HYPERLANE @@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ +@@@@@@@@@ @@@@@@@@*/ + // ============ Internal Imports ============ import {IMailbox} from "../interfaces/IMailbox.sol"; import {IPostDispatchHook} from "../interfaces/hooks/IPostDispatchHook.sol"; @@ -15,6 +27,9 @@ import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Own abstract contract MailboxClient is OwnableUpgradeable, PackageVersioned { using Message for bytes; + event HookSet(address _hook); + event IsmSet(address _ism); + IMailbox public immutable mailbox; uint32 public immutable localDomain; @@ -63,8 +78,11 @@ abstract contract MailboxClient is OwnableUpgradeable, PackageVersioned { * @notice Sets the address of the application's custom hook. * @param _hook The address of the hook contract. */ - function setHook(address _hook) public onlyContractOrNull(_hook) onlyOwner { + function setHook( + address _hook + ) public virtual onlyContractOrNull(_hook) onlyOwner { hook = IPostDispatchHook(_hook); + emit HookSet(_hook); } /** @@ -75,6 +93,7 @@ abstract contract MailboxClient is OwnableUpgradeable, PackageVersioned { address _module ) public onlyContractOrNull(_module) onlyOwner { interchainSecurityModule = IInterchainSecurityModule(_module); + emit IsmSet(_module); } // ======== Initializer ========= diff --git a/solidity/contracts/hooks/ProtocolFee.sol b/solidity/contracts/hooks/ProtocolFee.sol index 4e1f470482..b530b0d83e 100644 --- a/solidity/contracts/hooks/ProtocolFee.sol +++ b/solidity/contracts/hooks/ProtocolFee.sol @@ -32,6 +32,9 @@ contract ProtocolFee is AbstractPostDispatchHook, Ownable { using Address for address payable; using Message for bytes; + event ProtocolFeeSet(uint256 protocolFee); + event BeneficiarySet(address beneficiary); + // ============ Constants ============ /// @notice The maximum protocol fee that can be set. @@ -126,6 +129,7 @@ contract ProtocolFee is AbstractPostDispatchHook, Ownable { "ProtocolFee: exceeds max protocol fee" ); protocolFee = _protocolFee; + emit ProtocolFeeSet(_protocolFee); } /** @@ -135,5 +139,6 @@ contract ProtocolFee is AbstractPostDispatchHook, Ownable { function _setBeneficiary(address _beneficiary) internal { require(_beneficiary != address(0), "ProtocolFee: invalid beneficiary"); beneficiary = _beneficiary; + emit BeneficiarySet(_beneficiary); } } diff --git a/solidity/contracts/middleware/InterchainAccountRouter.sol b/solidity/contracts/middleware/InterchainAccountRouter.sol index 3980ac29bf..aa583f3410 100644 --- a/solidity/contracts/middleware/InterchainAccountRouter.sol +++ b/solidity/contracts/middleware/InterchainAccountRouter.sol @@ -22,6 +22,7 @@ import {TypeCasts} from "../libs/TypeCasts.sol"; import {StandardHookMetadata} from "../hooks/libs/StandardHookMetadata.sol"; import {EnumerableMapExtended} from "../libs/EnumerableMapExtended.sol"; import {Router} from "../client/Router.sol"; +import {IPostDispatchHook} from "../interfaces/hooks/IPostDispatchHook.sol"; // ============ External Imports ============ import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; @@ -160,6 +161,12 @@ contract InterchainAccountRouter is Router { } } + function setHook( + address _hook + ) public override onlyContractOrNull(_hook) onlyOwner { + hook = IPostDispatchHook(_hook); + } + // ============ External Functions ============ /** * @notice Dispatches a single remote call to be made by an owner's @@ -600,7 +607,14 @@ contract InterchainAccountRouter is Router { ) private returns (bytes32) { require(_router != bytes32(0), "no router specified for destination"); emit RemoteCallDispatched(_destination, msg.sender, _router, _ism); - return mailbox.dispatch{value: msg.value}(_destination, _router, _body); + return + mailbox.dispatch{value: msg.value}( + _destination, + _router, + _body, + new bytes(0), + hook + ); } /** @@ -625,7 +639,8 @@ contract InterchainAccountRouter is Router { _destination, _router, _body, - _hookMetadata + _hookMetadata, + hook ); } @@ -665,7 +680,13 @@ contract InterchainAccountRouter is Router { function quoteGasPayment( uint32 _destination ) external view returns (uint256 _gasPayment) { - return _quoteDispatch(_destination, ""); + return + _Router_quoteDispatch( + _destination, + new bytes(0), + new bytes(0), + address(hook) + ); } /** @@ -679,13 +700,12 @@ contract InterchainAccountRouter is Router { bytes calldata _messageBody, uint256 gasLimit ) external view returns (uint256 _gasPayment) { - bytes32 _router = _mustHaveRemoteRouter(_destination); return - mailbox.quoteDispatch( + _Router_quoteDispatch( _destination, - _router, _messageBody, - StandardHookMetadata.overrideGasLimit(gasLimit) + StandardHookMetadata.overrideGasLimit(gasLimit), + address(hook) ); } } diff --git a/solidity/contracts/test/TestPostDispatchHook.sol b/solidity/contracts/test/TestPostDispatchHook.sol index 34763b89c4..84c84f3792 100644 --- a/solidity/contracts/test/TestPostDispatchHook.sol +++ b/solidity/contracts/test/TestPostDispatchHook.sol @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity >=0.8.0; + import {Message} from "../libs/Message.sol"; import {IPostDispatchHook} from "../interfaces/hooks/IPostDispatchHook.sol"; @@ -35,15 +36,15 @@ contract TestPostDispatchHook is AbstractPostDispatchHook { // ============ Internal functions ============ function _postDispatch( - bytes calldata /*metadata*/, - bytes calldata message + bytes calldata, + /*metadata*/ bytes calldata message ) internal override { messageDispatched[message.id()] = true; } function _quoteDispatch( - bytes calldata /*metadata*/, - bytes calldata /*message*/ + bytes calldata, + /*metadata*/ bytes calldata /*message*/ ) internal view override returns (uint256) { return fee; } diff --git a/solidity/test/InterchainAccountRouter.t.sol b/solidity/test/InterchainAccountRouter.t.sol index d091bfe26a..65d804caa5 100644 --- a/solidity/test/InterchainAccountRouter.t.sol +++ b/solidity/test/InterchainAccountRouter.t.sol @@ -13,6 +13,8 @@ import {TestInterchainGasPaymaster} from "../contracts/test/TestInterchainGasPay import {IPostDispatchHook} from "../contracts/interfaces/hooks/IPostDispatchHook.sol"; import {CallLib, OwnableMulticall, InterchainAccountRouter} from "../contracts/middleware/InterchainAccountRouter.sol"; import {InterchainAccountIsm} from "../contracts/isms/routing/InterchainAccountIsm.sol"; +import {AbstractPostDispatchHook} from "../contracts/hooks/libs/AbstractPostDispatchHook.sol"; +import {TestPostDispatchHook} from "../contracts/test/TestPostDispatchHook.sol"; contract Callable { mapping(address => bytes32) public data; @@ -108,10 +110,11 @@ contract InterchainAccountRouterTestBase is Test { address owner = address(this); originIcaRouter = deployProxiedIcaRouter( environment.mailboxes(origin), - environment.igps(destination), + environment.igps(origin), icaIsm, owner ); + destinationIcaRouter = deployProxiedIcaRouter( environment.mailboxes(destination), environment.igps(destination), @@ -392,6 +395,25 @@ contract InterchainAccountRouterTest is InterchainAccountRouterTestBase { ); } + function test_quoteDispatch_differentHook() public { + // arrange + TestPostDispatchHook testHook = new TestPostDispatchHook(); + originIcaRouter = deployProxiedIcaRouter( + environment.mailboxes(origin), + testHook, + icaIsm, + address(this) + ); + originIcaRouter.enrollRemoteRouterAndIsm( + destination, + routerOverride, + ismOverride + ); + + // assert + assertEq(originIcaRouter.quoteGasPayment(destination), 0); + } + function testFuzz_singleCallRemoteWithDefault( bytes32 data, uint256 value @@ -444,6 +466,35 @@ contract InterchainAccountRouterTest is InterchainAccountRouterTestBase { assertIgpPayment(balanceBefore, balanceAfter, igp.getDefaultGasUsage()); } + function testFuzz_callRemoteWithDefault_differentHook( + bytes32 data, + uint256 value + ) public { + // arrange + TestPostDispatchHook testHook = new TestPostDispatchHook(); + originIcaRouter = deployProxiedIcaRouter( + environment.mailboxes(origin), + testHook, + icaIsm, + address(this) + ); + originIcaRouter.enrollRemoteRouterAndIsm( + destination, + routerOverride, + ismOverride + ); + + // assert + vm.expectCall( + address(testHook), + 0, + abi.encodePacked(AbstractPostDispatchHook.postDispatch.selector) + ); + + // act + originIcaRouter.callRemote(destination, getCalls(data, value)); + } + function testFuzz_overrideAndCallRemote( bytes32 data, uint256 value @@ -558,6 +609,7 @@ contract InterchainAccountRouterTest is InterchainAccountRouterTestBase { uint256 balanceAfter = address(this).balance; assertRemoteCallReceived(data, value); assertIgpPayment(balanceBefore, balanceAfter, igp.getDefaultGasUsage()); + assertEq(address(originIcaRouter.hook()), address(0)); } function testFuzz_callRemoteWithOverrides_metadata( @@ -591,6 +643,38 @@ contract InterchainAccountRouterTest is InterchainAccountRouterTestBase { assertIgpPayment(balanceBefore, balanceAfter, gasLimit); } + function testFuzz_callRemoteWithOverrides_withHook( + bytes32 data, + uint256 value + ) public { + TestPostDispatchHook testHook = new TestPostDispatchHook(); + + originIcaRouter = deployProxiedIcaRouter( + environment.mailboxes(origin), + testHook, + icaIsm, + address(this) + ); + originIcaRouter.enrollRemoteRouterAndIsm( + destination, + routerOverride, + ismOverride + ); + + vm.expectCall( + address(testHook), + 0, + abi.encodePacked(AbstractPostDispatchHook.postDispatch.selector) + ); + originIcaRouter.callRemoteWithOverrides( + destination, + routerOverride, + ismOverride, + getCalls(data, value), + new bytes(0) + ); + } + function testFuzz_callRemoteWithFailingIsmOverride( bytes32 data, uint256 value diff --git a/solidity/test/hooks/ProtocolFee.t.sol b/solidity/test/hooks/ProtocolFee.t.sol index df0d3471c0..bfd90f6872 100644 --- a/solidity/test/hooks/ProtocolFee.t.sol +++ b/solidity/test/hooks/ProtocolFee.t.sol @@ -42,6 +42,9 @@ contract ProtocolFeeTest is Test { function testSetProtocolFee(uint256 fee) public { fee = bound(fee, 0, fees.MAX_PROTOCOL_FEE()); + + vm.expectEmit(true, true, true, true); + emit ProtocolFee.ProtocolFeeSet(fee); fees.setProtocolFee(fee); assertEq(fees.protocolFee(), fee); } @@ -69,6 +72,13 @@ contract ProtocolFeeTest is Test { assertEq(fees.protocolFee(), FEE); } + function testSetBeneficiary(address beneficiary) public { + vm.expectEmit(true, true, true, true); + emit ProtocolFee.BeneficiarySet(beneficiary); + fees.setBeneficiary(beneficiary); + assertEq(fees.beneficiary(), beneficiary); + } + function testSetBeneficiary_revertWhen_notOwner() public { vm.prank(charlie); From d9505ab585637c8e7d2e31ee234fa968156f4c70 Mon Sep 17 00:00:00 2001 From: Paul Balaji <10051819+paulbalaji@users.noreply.github.com> Date: Tue, 29 Oct 2024 11:21:43 +0000 Subject: [PATCH 40/61] feat: oct 25 deployment batch (#4766) ### Description - feat: oct 25 deployment batch - apechain, arbitrumnova, b3, fantom, gravity, harmony, kaia, morph, orderly, snaxchain - deployed as normal - zeronetwork, zksync - deployed from special zksync branch containing txfusion's changes + latest `main` pulled in - https://github.com/hyperlane-xyz/hyperlane-monorepo/pull/4761 ### Drive-by changes - igp updates ### Related issues n/a ### Backward compatibility yes ### Testing manual using local CLI from this branch, no tx fusion changes required for hyperlane send or self-relay to work --- .changeset/fresh-pigs-work.md | 5 + .registryrc | 2 +- rust/main/config/mainnet_config.json | 785 +++++++++++++++- .../config/environments/mainnet3/agent.ts | 42 +- .../mainnet3/aw-validators/hyperlane.json | 36 + .../mainnet3/core/verification.json | 888 ++++++++++++++++++ .../config/environments/mainnet3/funding.ts | 14 +- .../environments/mainnet3/gasPrices.json | 100 +- .../mainnet3/ism/verification.json | 860 +++++++++++++++++ .../middleware/accounts/verification.json | 210 +++++ .../mainnet3/supportedChainNames.ts | 12 + .../environments/mainnet3/tokenPrices.json | 152 +-- .../environments/mainnet3/validators.ts | 122 +++ typescript/infra/scripts/deploy.ts | 4 + typescript/sdk/src/consts/multisigIsm.ts | 60 ++ typescript/sdk/src/core/HyperlaneCore.ts | 8 +- 16 files changed, 3196 insertions(+), 104 deletions(-) create mode 100644 .changeset/fresh-pigs-work.md diff --git a/.changeset/fresh-pigs-work.md b/.changeset/fresh-pigs-work.md new file mode 100644 index 0000000000..a43a7e3395 --- /dev/null +++ b/.changeset/fresh-pigs-work.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/sdk': minor +--- + +Deploy to apechain, arbitrumnova, b3, fantom, gravity, harmony, kaia, morph, orderly, snaxchain, zeronetwork, zksync. Update default metadata in `HyperlaneCore` to `0x00001` to ensure empty metadata does not break on zksync. diff --git a/.registryrc b/.registryrc index 265b470930..e1877ed1f1 100644 --- a/.registryrc +++ b/.registryrc @@ -1 +1 @@ -18666269e99ad8d2ba45579c4be783478e50e936 +302be4817c063629cec70c0b02322b250df71122 diff --git a/rust/main/config/mainnet_config.json b/rust/main/config/mainnet_config.json index fee83dfbd6..b0404fee40 100644 --- a/rust/main/config/mainnet_config.json +++ b/rust/main/config/mainnet_config.json @@ -3081,7 +3081,7 @@ "proxyAdmin": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", "rpcUrls": [ { - "http": "https://raas-backend.alchemy.com/rpc/worldchain-mainnet/rollup" + "http": "https://worldchain-mainnet.g.alchemy.com/public" } ], "staticAggregationHookFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", @@ -4843,6 +4843,789 @@ "from": 9152000, "chunk": 5 } + }, + "apechain": { + "blockExplorers": [ + { + "apiUrl": "https://apechain.calderaexplorer.xyz/api", + "family": "blockscout", + "name": "ApeChain Explorer", + "url": "https://apechain.calderaexplorer.xyz" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 0.2, + "reorgPeriod": 5 + }, + "chainId": 33139, + "deployer": { + "name": "Abacus Works", + "url": "https://www.hyperlane.xyz" + }, + "displayName": "ApeChain", + "domainId": 33139, + "gasCurrencyCoinGeckoId": "apecoin", + "index": { + "from": 1759561 + }, + "name": "apechain", + "nativeToken": { + "decimals": 18, + "name": "ApeCoin", + "symbol": "APE" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://rpc.apechain.com/http" + } + ], + "technicalStack": "arbitrumnitro", + "aggregationHook": "0x9C16FBa8b0c8a356E0f4398f524ae73Fd5a677B6", + "domainRoutingIsm": "0xDEed16fe4b1c9b2a93483EDFf34C77A9b57D31Ff", + "domainRoutingIsmFactory": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", + "fallbackRoutingHook": "0xD0dca420feFda68537695A8D887080eeF4030AF7", + "interchainAccountIsm": "0x6119B76720CcfeB3D256EC1b91218EEfFD6756E1", + "interchainAccountRouter": "0x9eaaC366BFD70430cFee6E70265fefFf1CfC9E47", + "interchainGasPaymaster": "0x18B0688990720103dB63559a3563f7E8d0f63EDb", + "interchainSecurityModule": "0x9FF3f38DED52D74EF4b666A7A09BcB5F38d6D272", + "mailbox": "0x7f50C5776722630a0024fAE05fDe8b47571D7B39", + "merkleTreeHook": "0x886BB0f329781b98f98FDeb1ce7a8957F2d43B9F", + "pausableHook": "0x2F619Ac5122689180AeBB930ADccdae215d538a9", + "pausableIsm": "0x5090dF2FBDa7127c7aDa41f60B79F5c55D380Dd8", + "protocolFee": "0x61374178e45F65fF9D6252d017Cd580FC60B7654", + "proxyAdmin": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", + "staticAggregationHookFactory": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "staticAggregationIsm": "0x9FF3f38DED52D74EF4b666A7A09BcB5F38d6D272", + "staticAggregationIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", + "staticMerkleRootMultisigIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", + "staticMerkleRootWeightedMultisigIsmFactory": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "staticMessageIdMultisigIsmFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", + "staticMessageIdWeightedMultisigIsmFactory": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", + "storageGasOracle": "0xF1854214392864c628A16930E73B699f7a51b3EE", + "testRecipient": "0x783EC5e105234a570eB90f314284E5dBe53bdd90", + "timelockController": "0x0000000000000000000000000000000000000000", + "validatorAnnounce": "0xcDA455DfD9C938451BfaFC6FF0D497c8C0469C96" + }, + "arbitrumnova": { + "blockExplorers": [ + { + "apiUrl": "https://api-nova.arbiscan.io/api", + "family": "etherscan", + "name": "Arbiscan Nova", + "url": "https://nova.arbiscan.io/" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 2, + "reorgPeriod": 5 + }, + "chainId": 42170, + "deployer": { + "name": "Abacus Works", + "url": "https://www.hyperlane.xyz" + }, + "displayName": "Arbitrum Nova", + "domainId": 42170, + "gasCurrencyCoinGeckoId": "ethereum", + "index": { + "from": 78794208 + }, + "name": "arbitrumnova", + "nativeToken": { + "decimals": 18, + "name": "Ethereum", + "symbol": "ETH" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://nova.arbitrum.io/rpc" + } + ], + "technicalStack": "arbitrumnitro", + "aggregationHook": "0xcFD1c5b1357539566edC273aDaae19CA5e359c42", + "domainRoutingIsm": "0x494415e823236A05c608D6b777bC80082cED6A2E", + "domainRoutingIsmFactory": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "fallbackRoutingHook": "0x886BB0f329781b98f98FDeb1ce7a8957F2d43B9F", + "interchainAccountIsm": "0xFB9e40D811Cea562cc8a322b029eF2BDcC3ef6ed", + "interchainAccountRouter": "0xeE8C0E1EeBfFCC451a013336386eA53E42a44451", + "interchainGasPaymaster": "0x145566181A18E23bB6a8A3eC6D87765542A7F754", + "interchainSecurityModule": "0x92772a801db50044a9D5078CC35CD63CEcD7B424", + "mailbox": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7", + "merkleTreeHook": "0x6963480b05EB58f4d624B014ab92e9aD4d21df6D", + "pausableHook": "0xD0dca420feFda68537695A8D887080eeF4030AF7", + "pausableIsm": "0x696df5e79C4f1bd5F8D587Ba8946361d9B029d4B", + "protocolFee": "0x13E83ac41e696856B6996263501fB3225AD5E6F5", + "proxyAdmin": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", + "staticAggregationHookFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", + "staticAggregationIsm": "0x92772a801db50044a9D5078CC35CD63CEcD7B424", + "staticAggregationIsmFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", + "staticMerkleRootMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", + "staticMerkleRootWeightedMultisigIsmFactory": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", + "staticMessageIdMultisigIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", + "staticMessageIdWeightedMultisigIsmFactory": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "storageGasOracle": "0x2F619Ac5122689180AeBB930ADccdae215d538a9", + "testRecipient": "0xcDA455DfD9C938451BfaFC6FF0D497c8C0469C96", + "timelockController": "0x0000000000000000000000000000000000000000", + "validatorAnnounce": "0x60B8d195f1b2EcaC26d54b95C69E6399cFD64b53" + }, + "b3": { + "blockExplorers": [ + { + "apiUrl": "https://explorer.b3.fun/api", + "family": "blockscout", + "name": "B3 Explorer", + "url": "https://explorer.b3.fun" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 1, + "reorgPeriod": 5 + }, + "chainId": 8333, + "deployer": { + "name": "Abacus Works", + "url": "https://www.hyperlane.xyz" + }, + "displayName": "B3", + "domainId": 8333, + "gasCurrencyCoinGeckoId": "ethereum", + "name": "b3", + "nativeToken": { + "decimals": 18, + "name": "Ethereum", + "symbol": "ETH" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://mainnet-rpc.b3.fun" + } + ], + "technicalStack": "opstack", + "aggregationHook": "0xcFD1c5b1357539566edC273aDaae19CA5e359c42", + "domainRoutingIsm": "0x494415e823236A05c608D6b777bC80082cED6A2E", + "domainRoutingIsmFactory": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "fallbackRoutingHook": "0x886BB0f329781b98f98FDeb1ce7a8957F2d43B9F", + "interchainAccountIsm": "0xFB9e40D811Cea562cc8a322b029eF2BDcC3ef6ed", + "interchainAccountRouter": "0xeE8C0E1EeBfFCC451a013336386eA53E42a44451", + "interchainGasPaymaster": "0x145566181A18E23bB6a8A3eC6D87765542A7F754", + "interchainSecurityModule": "0x92772a801db50044a9D5078CC35CD63CEcD7B424", + "mailbox": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7", + "merkleTreeHook": "0x6963480b05EB58f4d624B014ab92e9aD4d21df6D", + "pausableHook": "0xD0dca420feFda68537695A8D887080eeF4030AF7", + "pausableIsm": "0x696df5e79C4f1bd5F8D587Ba8946361d9B029d4B", + "protocolFee": "0x13E83ac41e696856B6996263501fB3225AD5E6F5", + "proxyAdmin": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", + "staticAggregationHookFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", + "staticAggregationIsm": "0x92772a801db50044a9D5078CC35CD63CEcD7B424", + "staticAggregationIsmFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", + "staticMerkleRootMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", + "staticMerkleRootWeightedMultisigIsmFactory": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", + "staticMessageIdMultisigIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", + "staticMessageIdWeightedMultisigIsmFactory": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "storageGasOracle": "0x2F619Ac5122689180AeBB930ADccdae215d538a9", + "testRecipient": "0xcDA455DfD9C938451BfaFC6FF0D497c8C0469C96", + "timelockController": "0x0000000000000000000000000000000000000000", + "validatorAnnounce": "0x60B8d195f1b2EcaC26d54b95C69E6399cFD64b53", + "index": { + "from": 7504261 + } + }, + "fantom": { + "blockExplorers": [ + { + "apiUrl": "https://api.ftmscan.com/api", + "family": "etherscan", + "name": "FTMScan", + "url": "https://ftmscan.com" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 2, + "reorgPeriod": 5 + }, + "chainId": 250, + "deployer": { + "name": "Abacus Works", + "url": "https://www.hyperlane.xyz" + }, + "displayName": "Fantom Opera", + "domainId": 250, + "gasCurrencyCoinGeckoId": "fantom", + "name": "fantom", + "nativeToken": { + "decimals": 18, + "name": "Fantom", + "symbol": "FTM" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://rpcapi.fantom.network" + }, + { + "http": "https://fantom-rpc.publicnode.com" + }, + { + "http": "https://fantom-pokt.nodies.app" + }, + { + "http": "https://rpc.fantom.network" + }, + { + "http": "https://rpc2.fantom.network" + }, + { + "http": "https://rpc3.fantom.network" + } + ], + "technicalStack": "other", + "aggregationHook": "0xcFD1c5b1357539566edC273aDaae19CA5e359c42", + "domainRoutingIsm": "0x494415e823236A05c608D6b777bC80082cED6A2E", + "domainRoutingIsmFactory": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "fallbackRoutingHook": "0x886BB0f329781b98f98FDeb1ce7a8957F2d43B9F", + "interchainAccountIsm": "0xFB9e40D811Cea562cc8a322b029eF2BDcC3ef6ed", + "interchainAccountRouter": "0xeE8C0E1EeBfFCC451a013336386eA53E42a44451", + "interchainGasPaymaster": "0x145566181A18E23bB6a8A3eC6D87765542A7F754", + "interchainSecurityModule": "0x92772a801db50044a9D5078CC35CD63CEcD7B424", + "mailbox": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7", + "merkleTreeHook": "0x6963480b05EB58f4d624B014ab92e9aD4d21df6D", + "pausableHook": "0xD0dca420feFda68537695A8D887080eeF4030AF7", + "pausableIsm": "0x696df5e79C4f1bd5F8D587Ba8946361d9B029d4B", + "protocolFee": "0x13E83ac41e696856B6996263501fB3225AD5E6F5", + "proxyAdmin": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", + "staticAggregationHookFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", + "staticAggregationIsm": "0x92772a801db50044a9D5078CC35CD63CEcD7B424", + "staticAggregationIsmFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", + "staticMerkleRootMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", + "staticMerkleRootWeightedMultisigIsmFactory": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", + "staticMessageIdMultisigIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", + "staticMessageIdWeightedMultisigIsmFactory": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "storageGasOracle": "0x2F619Ac5122689180AeBB930ADccdae215d538a9", + "testRecipient": "0xcDA455DfD9C938451BfaFC6FF0D497c8C0469C96", + "timelockController": "0x0000000000000000000000000000000000000000", + "validatorAnnounce": "0x60B8d195f1b2EcaC26d54b95C69E6399cFD64b53", + "index": { + "from": 95635171 + } + }, + "gravity": { + "blockExplorers": [ + { + "apiUrl": "https://explorer.gravity.xyz/api", + "family": "blockscout", + "name": "Gravity Alpha Explorer", + "url": "https://explorer.gravity.xyz" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 1, + "reorgPeriod": 5 + }, + "chainId": 1625, + "deployer": { + "name": "Abacus Works", + "url": "https://www.hyperlane.xyz" + }, + "displayName": "Gravity Alpha Mainnet", + "domainId": 1625, + "gasCurrencyCoinGeckoId": "g-token", + "index": { + "from": 13374779 + }, + "name": "gravity", + "nativeToken": { + "decimals": 18, + "name": "Gravity", + "symbol": "G" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://rpc.gravity.xyz" + } + ], + "technicalStack": "arbitrumnitro", + "aggregationHook": "0x836E1b748cac2FAc6264Baf2bF83cd9a79b723C6", + "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", + "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", + "fallbackRoutingHook": "0x6963480b05EB58f4d624B014ab92e9aD4d21df6D", + "interchainAccountIsm": "0x783EC5e105234a570eB90f314284E5dBe53bdd90", + "interchainAccountRouter": "0xc5D6aCaafBCcEC6D7fD7d92F4509befce641c563", + "interchainGasPaymaster": "0xf3dFf6747E7FC74B431C943961054B7BF6309d8a", + "interchainSecurityModule": "0xfa19BfEcB4fed2e0268ee5008a11cD946DcC13c3", + "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", + "merkleTreeHook": "0x5090dF2FBDa7127c7aDa41f60B79F5c55D380Dd8", + "pausableHook": "0x886BB0f329781b98f98FDeb1ce7a8957F2d43B9F", + "pausableIsm": "0x7621e04860F0bDe63311db9D5D8b589AD3458A1f", + "protocolFee": "0x8C3e1794018a589c9E9226b8543105fCb6cC88C4", + "proxyAdmin": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "staticAggregationHookFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", + "staticAggregationIsm": "0xfa19BfEcB4fed2e0268ee5008a11cD946DcC13c3", + "staticAggregationIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", + "staticMerkleRootMultisigIsmFactory": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", + "staticMerkleRootWeightedMultisigIsmFactory": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "staticMessageIdMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", + "staticMessageIdWeightedMultisigIsmFactory": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", + "storageGasOracle": "0xD0dca420feFda68537695A8D887080eeF4030AF7", + "testRecipient": "0x60B8d195f1b2EcaC26d54b95C69E6399cFD64b53", + "timelockController": "0x0000000000000000000000000000000000000000", + "validatorAnnounce": "0x61374178e45F65fF9D6252d017Cd580FC60B7654", + "displayNameShort": "Gravity" + }, + "harmony": { + "blockExplorers": [ + { + "apiUrl": "https://explorer.harmony.one/api", + "family": "blockscout", + "name": "Harmony Explorer", + "url": "https://explorer.harmony.one" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 2, + "reorgPeriod": 5 + }, + "chainId": 1666600000, + "deployer": { + "name": "Abacus Works", + "url": "https://www.hyperlane.xyz" + }, + "displayName": "Harmony One", + "domainId": 1666600000, + "gasCurrencyCoinGeckoId": "harmony", + "name": "harmony", + "nativeToken": { + "decimals": 18, + "name": "ONE", + "symbol": "ONE" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://api.harmony.one" + }, + { + "http": "https://api.s0.t.hmny.io" + }, + { + "http": "https://1rpc.io/one" + }, + { + "http": "https://rpc.ankr.com/harmony" + } + ], + "technicalStack": "other", + "aggregationHook": "0xcFD1c5b1357539566edC273aDaae19CA5e359c42", + "domainRoutingIsm": "0x494415e823236A05c608D6b777bC80082cED6A2E", + "domainRoutingIsmFactory": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "fallbackRoutingHook": "0x886BB0f329781b98f98FDeb1ce7a8957F2d43B9F", + "interchainAccountIsm": "0xFB9e40D811Cea562cc8a322b029eF2BDcC3ef6ed", + "interchainAccountRouter": "0xeE8C0E1EeBfFCC451a013336386eA53E42a44451", + "interchainGasPaymaster": "0x145566181A18E23bB6a8A3eC6D87765542A7F754", + "interchainSecurityModule": "0x92772a801db50044a9D5078CC35CD63CEcD7B424", + "mailbox": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7", + "merkleTreeHook": "0x6963480b05EB58f4d624B014ab92e9aD4d21df6D", + "pausableHook": "0xD0dca420feFda68537695A8D887080eeF4030AF7", + "pausableIsm": "0x696df5e79C4f1bd5F8D587Ba8946361d9B029d4B", + "protocolFee": "0x13E83ac41e696856B6996263501fB3225AD5E6F5", + "proxyAdmin": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", + "staticAggregationHookFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", + "staticAggregationIsm": "0x92772a801db50044a9D5078CC35CD63CEcD7B424", + "staticAggregationIsmFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", + "staticMerkleRootMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", + "staticMerkleRootWeightedMultisigIsmFactory": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", + "staticMessageIdMultisigIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", + "staticMessageIdWeightedMultisigIsmFactory": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "storageGasOracle": "0x2F619Ac5122689180AeBB930ADccdae215d538a9", + "testRecipient": "0xcDA455DfD9C938451BfaFC6FF0D497c8C0469C96", + "timelockController": "0x0000000000000000000000000000000000000000", + "validatorAnnounce": "0x60B8d195f1b2EcaC26d54b95C69E6399cFD64b53", + "index": { + "chunk": 999, + "from": 64597391 + } + }, + "kaia": { + "blockExplorers": [ + { + "apiUrl": "https://api-cypress.klaytnscope.com/api", + "family": "etherscan", + "name": "Kaiascope", + "url": "https://kaiascope.com" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 1, + "reorgPeriod": 5 + }, + "chainId": 8217, + "deployer": { + "name": "Abacus Works", + "url": "https://www.hyperlane.xyz" + }, + "displayName": "Kaia", + "domainId": 8217, + "gasCurrencyCoinGeckoId": "kaia", + "name": "kaia", + "nativeToken": { + "decimals": 18, + "name": "Kaia", + "symbol": "KLAY" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://public-en.node.kaia.io" + } + ], + "technicalStack": "other", + "aggregationHook": "0xcFD1c5b1357539566edC273aDaae19CA5e359c42", + "domainRoutingIsm": "0x494415e823236A05c608D6b777bC80082cED6A2E", + "domainRoutingIsmFactory": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "fallbackRoutingHook": "0x886BB0f329781b98f98FDeb1ce7a8957F2d43B9F", + "interchainAccountIsm": "0xFB9e40D811Cea562cc8a322b029eF2BDcC3ef6ed", + "interchainAccountRouter": "0xeE8C0E1EeBfFCC451a013336386eA53E42a44451", + "interchainGasPaymaster": "0x145566181A18E23bB6a8A3eC6D87765542A7F754", + "interchainSecurityModule": "0x92772a801db50044a9D5078CC35CD63CEcD7B424", + "mailbox": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7", + "merkleTreeHook": "0x6963480b05EB58f4d624B014ab92e9aD4d21df6D", + "pausableHook": "0xD0dca420feFda68537695A8D887080eeF4030AF7", + "pausableIsm": "0x696df5e79C4f1bd5F8D587Ba8946361d9B029d4B", + "protocolFee": "0x13E83ac41e696856B6996263501fB3225AD5E6F5", + "proxyAdmin": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", + "staticAggregationHookFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", + "staticAggregationIsm": "0x92772a801db50044a9D5078CC35CD63CEcD7B424", + "staticAggregationIsmFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", + "staticMerkleRootMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", + "staticMerkleRootWeightedMultisigIsmFactory": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", + "staticMessageIdMultisigIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", + "staticMessageIdWeightedMultisigIsmFactory": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "storageGasOracle": "0x2F619Ac5122689180AeBB930ADccdae215d538a9", + "testRecipient": "0xcDA455DfD9C938451BfaFC6FF0D497c8C0469C96", + "timelockController": "0x0000000000000000000000000000000000000000", + "validatorAnnounce": "0x60B8d195f1b2EcaC26d54b95C69E6399cFD64b53", + "index": { + "from": 167871545 + } + }, + "morph": { + "blockExplorers": [ + { + "apiUrl": "https://explorer-api.morphl2.io/api", + "family": "blockscout", + "name": "Morph Explorer", + "url": "https://explorer.morphl2.io" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 4, + "reorgPeriod": 5 + }, + "chainId": 2818, + "deployer": { + "name": "Abacus Works", + "url": "https://www.hyperlane.xyz" + }, + "displayName": "Morph", + "domainId": 2818, + "gasCurrencyCoinGeckoId": "ethereum", + "name": "morph", + "nativeToken": { + "decimals": 18, + "name": "Ether", + "symbol": "ETH" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://rpc.morphl2.io" + } + ], + "technicalStack": "other", + "aggregationHook": "0xcFD1c5b1357539566edC273aDaae19CA5e359c42", + "domainRoutingIsm": "0x494415e823236A05c608D6b777bC80082cED6A2E", + "domainRoutingIsmFactory": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "fallbackRoutingHook": "0x886BB0f329781b98f98FDeb1ce7a8957F2d43B9F", + "interchainAccountIsm": "0xFB9e40D811Cea562cc8a322b029eF2BDcC3ef6ed", + "interchainAccountRouter": "0xeE8C0E1EeBfFCC451a013336386eA53E42a44451", + "interchainGasPaymaster": "0x145566181A18E23bB6a8A3eC6D87765542A7F754", + "interchainSecurityModule": "0x92772a801db50044a9D5078CC35CD63CEcD7B424", + "mailbox": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7", + "merkleTreeHook": "0x6963480b05EB58f4d624B014ab92e9aD4d21df6D", + "pausableHook": "0xD0dca420feFda68537695A8D887080eeF4030AF7", + "pausableIsm": "0x696df5e79C4f1bd5F8D587Ba8946361d9B029d4B", + "protocolFee": "0x13E83ac41e696856B6996263501fB3225AD5E6F5", + "proxyAdmin": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", + "staticAggregationHookFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", + "staticAggregationIsm": "0x92772a801db50044a9D5078CC35CD63CEcD7B424", + "staticAggregationIsmFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", + "staticMerkleRootMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", + "staticMerkleRootWeightedMultisigIsmFactory": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", + "staticMessageIdMultisigIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", + "staticMessageIdWeightedMultisigIsmFactory": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "storageGasOracle": "0x2F619Ac5122689180AeBB930ADccdae215d538a9", + "testRecipient": "0xcDA455DfD9C938451BfaFC6FF0D497c8C0469C96", + "timelockController": "0x0000000000000000000000000000000000000000", + "validatorAnnounce": "0x60B8d195f1b2EcaC26d54b95C69E6399cFD64b53", + "index": { + "from": 94151 + } + }, + "orderly": { + "blockExplorers": [ + { + "apiUrl": "https://explorer.orderly.network/api", + "family": "blockscout", + "name": "Orderly L2 Explorer", + "url": "https://explorer.orderly.network" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 2, + "reorgPeriod": 5 + }, + "chainId": 291, + "deployer": { + "name": "Abacus Works", + "url": "https://www.hyperlane.xyz" + }, + "displayName": "Orderly L2", + "domainId": 291, + "gasCurrencyCoinGeckoId": "ethereum", + "name": "orderly", + "nativeToken": { + "decimals": 18, + "name": "Ethereum", + "symbol": "ETH" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://rpc.orderly.network" + }, + { + "http": "https://l2-orderly-mainnet-0.t.conduit.xyz" + } + ], + "technicalStack": "opstack", + "aggregationHook": "0xcFD1c5b1357539566edC273aDaae19CA5e359c42", + "domainRoutingIsm": "0x494415e823236A05c608D6b777bC80082cED6A2E", + "domainRoutingIsmFactory": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "fallbackRoutingHook": "0x886BB0f329781b98f98FDeb1ce7a8957F2d43B9F", + "interchainAccountIsm": "0xFB9e40D811Cea562cc8a322b029eF2BDcC3ef6ed", + "interchainAccountRouter": "0xeE8C0E1EeBfFCC451a013336386eA53E42a44451", + "interchainGasPaymaster": "0x145566181A18E23bB6a8A3eC6D87765542A7F754", + "interchainSecurityModule": "0x92772a801db50044a9D5078CC35CD63CEcD7B424", + "mailbox": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7", + "merkleTreeHook": "0x6963480b05EB58f4d624B014ab92e9aD4d21df6D", + "pausableHook": "0xD0dca420feFda68537695A8D887080eeF4030AF7", + "pausableIsm": "0x696df5e79C4f1bd5F8D587Ba8946361d9B029d4B", + "protocolFee": "0x13E83ac41e696856B6996263501fB3225AD5E6F5", + "proxyAdmin": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", + "staticAggregationHookFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", + "staticAggregationIsm": "0x92772a801db50044a9D5078CC35CD63CEcD7B424", + "staticAggregationIsmFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", + "staticMerkleRootMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", + "staticMerkleRootWeightedMultisigIsmFactory": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", + "staticMessageIdMultisigIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", + "staticMessageIdWeightedMultisigIsmFactory": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "storageGasOracle": "0x2F619Ac5122689180AeBB930ADccdae215d538a9", + "testRecipient": "0xcDA455DfD9C938451BfaFC6FF0D497c8C0469C96", + "timelockController": "0x0000000000000000000000000000000000000000", + "validatorAnnounce": "0x60B8d195f1b2EcaC26d54b95C69E6399cFD64b53", + "index": { + "from": 16635646 + } + }, + "snaxchain": { + "blockExplorers": [ + { + "apiUrl": "https://explorer.snaxchain.io/api", + "family": "blockscout", + "name": "Snaxchain Mainnet Explorer", + "url": "https://explorer.snaxchain.io" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 2, + "reorgPeriod": 5 + }, + "chainId": 2192, + "deployer": { + "name": "Abacus Works", + "url": "https://www.hyperlane.xyz" + }, + "displayName": "SnaxChain", + "domainId": 2192, + "gasCurrencyCoinGeckoId": "ethereum", + "name": "snaxchain", + "nativeToken": { + "decimals": 18, + "name": "Ether", + "symbol": "ETH" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://mainnet.snaxchain.io" + } + ], + "technicalStack": "opstack", + "aggregationHook": "0xcFD1c5b1357539566edC273aDaae19CA5e359c42", + "domainRoutingIsm": "0x494415e823236A05c608D6b777bC80082cED6A2E", + "domainRoutingIsmFactory": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "fallbackRoutingHook": "0x886BB0f329781b98f98FDeb1ce7a8957F2d43B9F", + "interchainAccountIsm": "0xFB9e40D811Cea562cc8a322b029eF2BDcC3ef6ed", + "interchainAccountRouter": "0xeE8C0E1EeBfFCC451a013336386eA53E42a44451", + "interchainGasPaymaster": "0x145566181A18E23bB6a8A3eC6D87765542A7F754", + "interchainSecurityModule": "0x92772a801db50044a9D5078CC35CD63CEcD7B424", + "mailbox": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7", + "merkleTreeHook": "0x6963480b05EB58f4d624B014ab92e9aD4d21df6D", + "pausableHook": "0xD0dca420feFda68537695A8D887080eeF4030AF7", + "pausableIsm": "0x696df5e79C4f1bd5F8D587Ba8946361d9B029d4B", + "protocolFee": "0x13E83ac41e696856B6996263501fB3225AD5E6F5", + "proxyAdmin": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", + "staticAggregationHookFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", + "staticAggregationIsm": "0x92772a801db50044a9D5078CC35CD63CEcD7B424", + "staticAggregationIsmFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", + "staticMerkleRootMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", + "staticMerkleRootWeightedMultisigIsmFactory": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", + "staticMessageIdMultisigIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", + "staticMessageIdWeightedMultisigIsmFactory": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "storageGasOracle": "0x2F619Ac5122689180AeBB930ADccdae215d538a9", + "testRecipient": "0xcDA455DfD9C938451BfaFC6FF0D497c8C0469C96", + "timelockController": "0x0000000000000000000000000000000000000000", + "validatorAnnounce": "0x60B8d195f1b2EcaC26d54b95C69E6399cFD64b53", + "index": { + "from": 3158644 + } + }, + "zeronetwork": { + "blockExplorers": [ + { + "apiUrl": "https://zero-network-api.calderaexplorer.xyz/api", + "family": "etherscan", + "name": "Zero Network Explorer", + "url": "https://zerion-explorer.vercel.app" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 1, + "reorgPeriod": 0 + }, + "chainId": 543210, + "deployer": { + "name": "Abacus Works", + "url": "https://www.hyperlane.xyz" + }, + "displayName": "Zero Network", + "domainId": 543210, + "gasCurrencyCoinGeckoId": "ethereum", + "name": "zeronetwork", + "nativeToken": { + "decimals": 18, + "name": "Ether", + "symbol": "ETH" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://zero-network.calderachain.xyz" + } + ], + "technicalStack": "zksync", + "domainRoutingIsm": "0x307A9dBD1df2329c3c597aF6853de60660baFFb5", + "domainRoutingIsmFactory": "0x0000000000000000000000000000000000000000", + "fallbackDomainRoutingHook": "0x671836d35BB15E21ECc92c4936F0e3131efe12B4", + "fallbackRoutingHook": "0x671836d35BB15E21ECc92c4936F0e3131efe12B4", + "interchainGasPaymaster": "0x318FbdB17d4e743aBF3183658a4730777101B75C", + "interchainSecurityModule": "0x307A9dBD1df2329c3c597aF6853de60660baFFb5", + "mailbox": "0xd7b351D2dE3495eA259DD10ab4b9300A378Afbf3", + "merkleTreeHook": "0x55379421409961Ef129738c24261379ef8A547Df", + "proxyAdmin": "0x72e2A678442Edc65f14476A0E4c94312C0469f4A", + "staticAggregationHookFactory": "0x0000000000000000000000000000000000000000", + "staticAggregationIsmFactory": "0x0000000000000000000000000000000000000000", + "staticMerkleRootMultisigIsmFactory": "0x0000000000000000000000000000000000000000", + "staticMerkleRootWeightedMultisigIsmFactory": "0x0000000000000000000000000000000000000000", + "staticMessageIdMultisigIsmFactory": "0x0000000000000000000000000000000000000000", + "staticMessageIdWeightedMultisigIsmFactory": "0x0000000000000000000000000000000000000000", + "storageGasOracle": "0xe85d65f04D1562f8571d57326d6798e4584aa254", + "testRecipient": "0xC18bE7ac43334F501fd9622877160b085215dECC", + "validatorAnnounce": "0xB2F0e411B46AbE3248dAFB5e89aDB5b8404F45DF", + "index": { + "from": 475 + } + }, + "zksync": { + "blockExplorers": [ + { + "apiUrl": "https://block-explorer-api.mainnet.zksync.io/api", + "family": "etherscan", + "name": "zkSync Explorer", + "url": "https://explorer.zksync.io" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 1, + "reorgPeriod": 0 + }, + "chainId": 324, + "deployer": { + "name": "Abacus Works", + "url": "https://www.hyperlane.xyz" + }, + "displayName": "zkSync", + "domainId": 324, + "gasCurrencyCoinGeckoId": "ethereum", + "name": "zksync", + "nativeToken": { + "decimals": 18, + "name": "Ether", + "symbol": "ETH" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://mainnet.era.zksync.io" + } + ], + "technicalStack": "zksync", + "domainRoutingIsm": "0xec650696FDAE2355A928520AD7d6491c6072cf7f", + "domainRoutingIsmFactory": "0x0000000000000000000000000000000000000000", + "fallbackDomainRoutingHook": "0xe4e98Cc5D0318aBFD2adA8A3C6817b727063F500", + "fallbackRoutingHook": "0xe4e98Cc5D0318aBFD2adA8A3C6817b727063F500", + "interchainGasPaymaster": "0xf44AdA86a1f765A938d404699B8070Dd47bD2431", + "interchainSecurityModule": "0xec650696FDAE2355A928520AD7d6491c6072cf7f", + "mailbox": "0x6bD0A2214797Bc81e0b006F7B74d6221BcD8cb6E", + "merkleTreeHook": "0x823500D69D77A52212DC93f8836E9c08581487eE", + "proxyAdmin": "0xD01274DC164D32F8595bE707F221375E68cE300C", + "staticAggregationHookFactory": "0x0000000000000000000000000000000000000000", + "staticAggregationIsmFactory": "0x0000000000000000000000000000000000000000", + "staticMerkleRootMultisigIsmFactory": "0x0000000000000000000000000000000000000000", + "staticMerkleRootWeightedMultisigIsmFactory": "0x0000000000000000000000000000000000000000", + "staticMessageIdMultisigIsmFactory": "0x0000000000000000000000000000000000000000", + "staticMessageIdWeightedMultisigIsmFactory": "0x0000000000000000000000000000000000000000", + "storageGasOracle": "0x37f4afe769087738f0577A77ffA24abef6fCBF99", + "testRecipient": "0xD55078c54b0cEAa87Ba5c3fAeAC89861c69F636d", + "validatorAnnounce": "0x576aF402c97bFE452Dcc203B6c3f6F4EBC92A0f5", + "index": { + "from": 47325797 + } } }, "defaultRpcConsensusType": "fallback" diff --git a/typescript/infra/config/environments/mainnet3/agent.ts b/typescript/infra/config/environments/mainnet3/agent.ts index 68ac9ccb9a..aa79f5e432 100644 --- a/typescript/infra/config/environments/mainnet3/agent.ts +++ b/typescript/infra/config/environments/mainnet3/agent.ts @@ -58,10 +58,13 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig< [Role.Validator]: { ancient8: true, alephzeroevm: true, + apechain: true, arbitrum: true, + arbitrumnova: true, astar: true, astarzkevm: true, avalanche: true, + b3: true, base: true, bitlayer: true, blast: true, @@ -78,14 +81,18 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig< endurance: true, ethereum: true, everclear: true, + fantom: true, flare: true, flow: true, fraxtal: true, fusemainnet: true, gnosis: true, + gravity: true, + harmony: true, immutablezkevm: true, inevm: true, injective: true, + kaia: true, kroma: true, linea: true, lisk: true, @@ -100,9 +107,11 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig< mode: true, molten: true, moonbeam: true, + morph: true, neutron: true, oortmainnet: true, optimism: true, + orderly: true, osmosis: true, polygon: true, polygonzkevm: true, @@ -116,6 +125,7 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig< scroll: true, sei: true, shibarium: true, + snaxchain: true, solanamainnet: true, stride: false, superposition: true, @@ -125,17 +135,22 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig< worldchain: true, xai: true, xlayer: true, + zeronetwork: true, zetachain: true, zircuit: true, + zksync: true, zoramainnet: true, }, [Role.Relayer]: { alephzeroevm: true, ancient8: true, + apechain: true, arbitrum: true, + arbitrumnova: true, astar: true, astarzkevm: true, avalanche: true, + b3: true, base: true, bitlayer: true, blast: true, @@ -152,14 +167,18 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig< endurance: true, ethereum: true, everclear: true, + fantom: true, flare: true, flow: true, fraxtal: true, fusemainnet: true, gnosis: true, + gravity: true, + harmony: true, immutablezkevm: true, inevm: true, injective: true, + kaia: true, kroma: true, linea: true, lisk: true, @@ -174,10 +193,12 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig< mode: true, molten: true, moonbeam: true, + morph: true, // At the moment, we only relay between Neutron and Manta Pacific on the neutron context. neutron: false, oortmainnet: true, optimism: true, + orderly: true, osmosis: true, polygon: true, polygonzkevm: true, @@ -191,6 +212,7 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig< scroll: true, sei: true, shibarium: true, + snaxchain: true, solanamainnet: true, stride: true, superposition: true, @@ -200,17 +222,22 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig< worldchain: true, xai: true, xlayer: true, + zeronetwork: true, zetachain: true, zircuit: true, + zksync: true, zoramainnet: true, }, [Role.Scraper]: { ancient8: true, alephzeroevm: true, + apechain: true, arbitrum: true, + arbitrumnova: true, astar: true, astarzkevm: true, avalanche: true, + b3: true, base: true, bitlayer: true, blast: true, @@ -228,14 +255,18 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig< endurance: true, ethereum: true, everclear: true, + fantom: true, flare: true, flow: true, fraxtal: true, fusemainnet: true, gnosis: true, + gravity: true, + harmony: true, immutablezkevm: true, inevm: true, injective: true, + kaia: true, kroma: true, linea: true, lisk: true, @@ -250,9 +281,11 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig< mode: true, molten: true, moonbeam: true, + morph: true, neutron: true, oortmainnet: true, optimism: true, + orderly: true, osmosis: true, polygon: true, polygonzkevm: true, @@ -266,6 +299,7 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig< scroll: true, sei: true, shibarium: true, + snaxchain: true, // Cannot scrape Sealevel chains solanamainnet: false, stride: true, @@ -277,8 +311,10 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig< worldchain: true, xai: true, xlayer: true, + zeronetwork: true, zetachain: true, zircuit: true, + zksync: true, zoramainnet: true, }, }; @@ -429,7 +465,7 @@ const hyperlane: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: 'a64af8b-20241024-120818', + tag: '45399a3-20241025-210128', }, gasPaymentEnforcement: gasPaymentEnforcement, metricAppContexts, @@ -438,7 +474,7 @@ const hyperlane: RootAgentConfig = { validators: { docker: { repo, - tag: 'a64af8b-20241024-120818', + tag: '45399a3-20241025-210128', }, rpcConsensusType: RpcConsensusType.Quorum, chains: validatorChainConfig(Contexts.Hyperlane), @@ -448,7 +484,7 @@ const hyperlane: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: 'a64af8b-20241024-120818', + tag: '45399a3-20241025-210128', }, resources: scraperResources, }, diff --git a/typescript/infra/config/environments/mainnet3/aw-validators/hyperlane.json b/typescript/infra/config/environments/mainnet3/aw-validators/hyperlane.json index a63b630da9..67119a8ee2 100644 --- a/typescript/infra/config/environments/mainnet3/aw-validators/hyperlane.json +++ b/typescript/infra/config/environments/mainnet3/aw-validators/hyperlane.json @@ -5,6 +5,9 @@ "alephzeroevm": { "validators": ["0xcae8fab142adc4e434bb7409e40dd932cc3851aa"] }, + "apechain": { + "validators": ["0x773d7fe6ffb1ba4de814c28044ff9a2d83a48221"] + }, "arbitrum": { "validators": [ "0x4d966438fe9e2b1e7124c87bbb90cb4f0f6c59a1", @@ -12,6 +15,9 @@ "0x3369e12edd52570806f126eb50be269ba5e65843" ] }, + "arbitrumnova": { + "validators": ["0xd2a5e9123308d187383c87053811a2c21bd8af1f"] + }, "astar": { "validators": ["0x4d1b2cade01ee3493f44304653d8e352c66ec3e7"] }, @@ -25,6 +31,9 @@ "0x6c754f1e9cd8287088b46a7c807303d55d728b49" ] }, + "b3": { + "validators": ["0xd77b516730a836fc41934e7d5864e72c165b934e"] + }, "base": { "validators": [ "0xb9453d675e0fa3c178a17b4ce1ad5b1a279b3af9", @@ -89,6 +98,9 @@ "everclear": { "validators": ["0xeff20ae3d5ab90abb11e882cfce4b92ea6c74837"] }, + "fantom": { + "validators": ["0xa779572028e634e16f26af5dfd4fa685f619457d"] + }, "flare": { "validators": ["0xb65e52be342dba3ab2c088ceeb4290c744809134"] }, @@ -108,6 +120,12 @@ "0xb93a72cee19402553c9dd7fed2461aebd04e2454" ] }, + "gravity": { + "validators": ["0x23d549bf757a02a6f6068e9363196ecd958c974e"] + }, + "harmony": { + "validators": ["0xd677803a67651974b1c264171b5d7ca8838db8d5"] + }, "immutablezkevm": { "validators": ["0xa787c2952a4d22f776ee6e87e828e6f75de24330"] }, @@ -121,6 +139,9 @@ "injective": { "validators": ["0xbfb8911b72cfb138c7ce517c57d9c691535dc517"] }, + "kaia": { + "validators": ["0x9de0b3abb221d19719882fa4d61f769fdc2be9a4"] + }, "kroma": { "validators": ["0x71b83c21342787d758199e4b8634d3a15f02dc6e"] }, @@ -171,6 +192,9 @@ "0xcc4a78aa162482bea43313cd836ba7b560b44fc4" ] }, + "morph": { + "validators": ["0x4884535f393151ec419add872100d352f71af380"] + }, "neutron": { "validators": [ "0xa9b8c1f4998f781f958c63cfcd1708d02f004ff0", @@ -188,6 +212,9 @@ "0x779a17e035018396724a6dec8a59bda1b5adf738" ] }, + "orderly": { + "validators": ["0xec3dc91f9fa2ad35edf5842aa764d5573b778bb6"] + }, "osmosis": { "validators": ["0xea483af11c19fa41b16c31d1534c2a486a92bcac"] }, @@ -239,6 +266,9 @@ "shibarium": { "validators": ["0xfa33391ee38597cbeef72ccde8c9e13e01e78521"] }, + "snaxchain": { + "validators": ["0x2c25829ae32a772d2a49f6c4b34f8b01fd03ef9e"] + }, "solanamainnet": { "validators": ["0x28464752829b3ea59a497fca0bdff575c534c3ff"] }, @@ -263,12 +293,18 @@ "xlayer": { "validators": ["0xa2ae7c594703e988f23d97220717c513db638ea3"] }, + "zeronetwork": { + "validators": ["0x1bd9e3f8a90ea1a13b0f2838a1858046368aad87"] + }, "zetachain": { "validators": ["0xa3bca0b80317dbf9c7dce16a16ac89f4ff2b23ef"] }, "zircuit": { "validators": ["0x169ec400cc758fef3df6a0d6c51fbc6cdd1015bb"] }, + "zksync": { + "validators": ["0xadd1d39ce7a687e32255ac457cf99a6d8c5b5d1a"] + }, "zoramainnet": { "validators": ["0x35130945b625bb69b28aee902a3b9a76fa67125f"] } diff --git a/typescript/infra/config/environments/mainnet3/core/verification.json b/typescript/infra/config/environments/mainnet3/core/verification.json index 371e684030..029f75ad64 100644 --- a/typescript/infra/config/environments/mainnet3/core/verification.json +++ b/typescript/infra/config/environments/mainnet3/core/verification.json @@ -4395,5 +4395,893 @@ "constructorArguments": "000000000000000000000000783ec5e105234a570eb90f314284e5dbe53bdd90", "isProxy": false } + ], + "zksync": [ + { + "name": "ProxyAdmin", + "address": "0xD01274DC164D32F8595bE707F221375E68cE300C", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "Mailbox", + "address": "0x1B4E7fd3052Fc6d84DAB69eEf6a156C7D7909a78", + "constructorArguments": "0000000000000000000000000000000000000000000000000000000000000144", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0x6bD0A2214797Bc81e0b006F7B74d6221BcD8cb6E", + "constructorArguments": "0000000000000000000000001b4e7fd3052fc6d84dab69eef6a156c7d7909a78000000000000000000000000d01274dc164d32f8595be707f221375e68ce300c00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "expectedimplementation": "0x1B4E7fd3052Fc6d84DAB69eEf6a156C7D7909a78" + }, + { + "name": "DomainRoutingIsm", + "address": "0xec650696FDAE2355A928520AD7d6491c6072cf7f", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "MerkleTreeHook", + "address": "0x823500D69D77A52212DC93f8836E9c08581487eE", + "constructorArguments": "0000000000000000000000006bd0a2214797bc81e0b006f7b74d6221bcd8cb6e", + "isProxy": false + }, + { + "name": "FallbackDomainRoutingHook", + "address": "0xe4e98Cc5D0318aBFD2adA8A3C6817b727063F500", + "constructorArguments": "0000000000000000000000006bd0a2214797bc81e0b006f7b74d6221bcd8cb6e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000823500d69d77a52212dc93f8836e9c08581487ee", + "isProxy": false + }, + { + "name": "StorageGasOracle", + "address": "0x37f4afe769087738f0577A77ffA24abef6fCBF99", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "InterchainGasPaymaster", + "address": "0x95d20666eDf61b39f2706a7fc95E50C2758F800b", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0xf44AdA86a1f765A938d404699B8070Dd47bD2431", + "constructorArguments": "00000000000000000000000095d20666edf61b39f2706a7fc95e50c2758f800b000000000000000000000000d01274dc164d32f8595be707f221375e68ce300c00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "expectedimplementation": "0x95d20666eDf61b39f2706a7fc95E50C2758F800b" + }, + { + "name": "ValidatorAnnounce", + "address": "0x576aF402c97bFE452Dcc203B6c3f6F4EBC92A0f5", + "constructorArguments": "0000000000000000000000006bd0a2214797bc81e0b006f7b74d6221bcd8cb6e", + "isProxy": false + } + ], + "zeronetwork": [ + { + "name": "ProxyAdmin", + "address": "0x72e2A678442Edc65f14476A0E4c94312C0469f4A", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "Mailbox", + "address": "0x3a4c1e089dCaba813c10b641d8296a972ffAd939", + "constructorArguments": "00000000000000000000000000000000000000000000000000000000000849ea", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0xd7b351D2dE3495eA259DD10ab4b9300A378Afbf3", + "constructorArguments": "0000000000000000000000003a4c1e089dcaba813c10b641d8296a972ffad93900000000000000000000000072e2a678442edc65f14476a0e4c94312c0469f4a00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "expectedimplementation": "0x3a4c1e089dCaba813c10b641d8296a972ffAd939" + }, + { + "name": "DomainRoutingIsm", + "address": "0x307A9dBD1df2329c3c597aF6853de60660baFFb5", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "MerkleTreeHook", + "address": "0x55379421409961Ef129738c24261379ef8A547Df", + "constructorArguments": "000000000000000000000000d7b351d2de3495ea259dd10ab4b9300a378afbf3", + "isProxy": false + }, + { + "name": "FallbackDomainRoutingHook", + "address": "0x671836d35BB15E21ECc92c4936F0e3131efe12B4", + "constructorArguments": "000000000000000000000000d7b351d2de3495ea259dd10ab4b9300a378afbf3000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000055379421409961ef129738c24261379ef8a547df", + "isProxy": false + }, + { + "name": "StorageGasOracle", + "address": "0xe85d65f04D1562f8571d57326d6798e4584aa254", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "InterchainGasPaymaster", + "address": "0xDA6193892B324fdEc49209E7cB37E5eE84Cb2459", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0x318FbdB17d4e743aBF3183658a4730777101B75C", + "constructorArguments": "000000000000000000000000da6193892b324fdec49209e7cb37e5ee84cb245900000000000000000000000072e2a678442edc65f14476a0e4c94312c0469f4a00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "expectedimplementation": "0xDA6193892B324fdEc49209E7cB37E5eE84Cb2459" + }, + { + "name": "ValidatorAnnounce", + "address": "0xB2F0e411B46AbE3248dAFB5e89aDB5b8404F45DF", + "constructorArguments": "000000000000000000000000d7b351d2de3495ea259dd10ab4b9300a378afbf3", + "isProxy": false + } + ], + "gravity": [ + { + "name": "ProxyAdmin", + "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "Mailbox", + "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", + "constructorArguments": "0000000000000000000000000000000000000000000000000000000000000659", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", + "constructorArguments": "000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "expectedimplementation": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D" + }, + { + "name": "PausableIsm", + "address": "0x7621e04860F0bDe63311db9D5D8b589AD3458A1f", + "constructorArguments": "000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "isProxy": false + }, + { + "name": "MerkleTreeHook", + "address": "0x5090dF2FBDa7127c7aDa41f60B79F5c55D380Dd8", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", + "isProxy": false + }, + { + "name": "FallbackRoutingHook", + "address": "0x6963480b05EB58f4d624B014ab92e9aD4d21df6D", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000005090df2fbda7127c7ada41f60b79f5c55d380dd8", + "isProxy": false + }, + { + "name": "PausableHook", + "address": "0x886BB0f329781b98f98FDeb1ce7a8957F2d43B9F", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StorageGasOracle", + "address": "0xD0dca420feFda68537695A8D887080eeF4030AF7", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "InterchainGasPaymaster", + "address": "0xF1854214392864c628A16930E73B699f7a51b3EE", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0xf3dFf6747E7FC74B431C943961054B7BF6309d8a", + "constructorArguments": "000000000000000000000000f1854214392864c628a16930e73b699f7a51b3ee0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "expectedimplementation": "0xF1854214392864c628A16930E73B699f7a51b3EE" + }, + { + "name": "ProtocolFee", + "address": "0x8C3e1794018a589c9E9226b8543105fCb6cC88C4", + "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "isProxy": false + }, + { + "name": "ValidatorAnnounce", + "address": "0x61374178e45F65fF9D6252d017Cd580FC60B7654", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", + "isProxy": false + } + ], + "arbitrumnova": [ + { + "name": "ProxyAdmin", + "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "Mailbox", + "address": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", + "constructorArguments": "000000000000000000000000000000000000000000000000000000000000a4ba", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "expectedimplementation": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E" + }, + { + "name": "PausableIsm", + "address": "0x696df5e79C4f1bd5F8D587Ba8946361d9B029d4B", + "constructorArguments": "000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "isProxy": false + }, + { + "name": "MerkleTreeHook", + "address": "0x6963480b05EB58f4d624B014ab92e9aD4d21df6D", + "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa7", + "isProxy": false + }, + { + "name": "FallbackRoutingHook", + "address": "0x886BB0f329781b98f98FDeb1ce7a8957F2d43B9F", + "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa7000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000006963480b05eb58f4d624b014ab92e9ad4d21df6d", + "isProxy": false + }, + { + "name": "PausableHook", + "address": "0xD0dca420feFda68537695A8D887080eeF4030AF7", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StorageGasOracle", + "address": "0x2F619Ac5122689180AeBB930ADccdae215d538a9", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "InterchainGasPaymaster", + "address": "0xEe08043cf22c80b27BF24d19999231dF4a3fC256", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0x145566181A18E23bB6a8A3eC6D87765542A7F754", + "constructorArguments": "000000000000000000000000ee08043cf22c80b27bf24d19999231df4a3fc256000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "expectedimplementation": "0xEe08043cf22c80b27BF24d19999231dF4a3fC256" + }, + { + "name": "ProtocolFee", + "address": "0x13E83ac41e696856B6996263501fB3225AD5E6F5", + "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "isProxy": false + }, + { + "name": "ValidatorAnnounce", + "address": "0x60B8d195f1b2EcaC26d54b95C69E6399cFD64b53", + "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa7", + "isProxy": false + } + ], + "apechain": [ + { + "name": "ProxyAdmin", + "address": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "Mailbox", + "address": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7", + "constructorArguments": "0000000000000000000000000000000000000000000000000000000000008173", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0x7f50C5776722630a0024fAE05fDe8b47571D7B39", + "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa70000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "expectedimplementation": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7" + }, + { + "name": "PausableIsm", + "address": "0x5090dF2FBDa7127c7aDa41f60B79F5c55D380Dd8", + "constructorArguments": "000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "isProxy": false + }, + { + "name": "MerkleTreeHook", + "address": "0x886BB0f329781b98f98FDeb1ce7a8957F2d43B9F", + "constructorArguments": "0000000000000000000000007f50c5776722630a0024fae05fde8b47571d7b39", + "isProxy": false + }, + { + "name": "FallbackRoutingHook", + "address": "0xD0dca420feFda68537695A8D887080eeF4030AF7", + "constructorArguments": "0000000000000000000000007f50c5776722630a0024fae05fde8b47571d7b39000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000886bb0f329781b98f98fdeb1ce7a8957f2d43b9f", + "isProxy": false + }, + { + "name": "PausableHook", + "address": "0x2F619Ac5122689180AeBB930ADccdae215d538a9", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StorageGasOracle", + "address": "0xF1854214392864c628A16930E73B699f7a51b3EE", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "InterchainGasPaymaster", + "address": "0xf3dFf6747E7FC74B431C943961054B7BF6309d8a", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0x18B0688990720103dB63559a3563f7E8d0f63EDb", + "constructorArguments": "000000000000000000000000f3dff6747e7fc74b431c943961054b7bf6309d8a0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "expectedimplementation": "0xf3dFf6747E7FC74B431C943961054B7BF6309d8a" + }, + { + "name": "ProtocolFee", + "address": "0x61374178e45F65fF9D6252d017Cd580FC60B7654", + "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "isProxy": false + }, + { + "name": "ValidatorAnnounce", + "address": "0xcDA455DfD9C938451BfaFC6FF0D497c8C0469C96", + "constructorArguments": "0000000000000000000000007f50c5776722630a0024fae05fde8b47571d7b39", + "isProxy": false + } + ], + "harmony": [ + { + "name": "ProxyAdmin", + "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "Mailbox", + "address": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", + "constructorArguments": "0000000000000000000000000000000000000000000000000000000063564c40", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "expectedimplementation": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E" + }, + { + "name": "PausableIsm", + "address": "0x696df5e79C4f1bd5F8D587Ba8946361d9B029d4B", + "constructorArguments": "000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "isProxy": false + }, + { + "name": "MerkleTreeHook", + "address": "0x6963480b05EB58f4d624B014ab92e9aD4d21df6D", + "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa7", + "isProxy": false + }, + { + "name": "FallbackRoutingHook", + "address": "0x886BB0f329781b98f98FDeb1ce7a8957F2d43B9F", + "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa7000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000006963480b05eb58f4d624b014ab92e9ad4d21df6d", + "isProxy": false + }, + { + "name": "PausableHook", + "address": "0xD0dca420feFda68537695A8D887080eeF4030AF7", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StorageGasOracle", + "address": "0x2F619Ac5122689180AeBB930ADccdae215d538a9", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "InterchainGasPaymaster", + "address": "0xEe08043cf22c80b27BF24d19999231dF4a3fC256", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0x145566181A18E23bB6a8A3eC6D87765542A7F754", + "constructorArguments": "000000000000000000000000ee08043cf22c80b27bf24d19999231df4a3fc256000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "expectedimplementation": "0xEe08043cf22c80b27BF24d19999231dF4a3fC256" + }, + { + "name": "ProtocolFee", + "address": "0x13E83ac41e696856B6996263501fB3225AD5E6F5", + "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "isProxy": false + }, + { + "name": "ValidatorAnnounce", + "address": "0x60B8d195f1b2EcaC26d54b95C69E6399cFD64b53", + "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa7", + "isProxy": false + } + ], + "kaia": [ + { + "name": "ProxyAdmin", + "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "Mailbox", + "address": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", + "constructorArguments": "0000000000000000000000000000000000000000000000000000000000002019", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "expectedimplementation": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E" + }, + { + "name": "PausableIsm", + "address": "0x696df5e79C4f1bd5F8D587Ba8946361d9B029d4B", + "constructorArguments": "000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "isProxy": false + }, + { + "name": "MerkleTreeHook", + "address": "0x6963480b05EB58f4d624B014ab92e9aD4d21df6D", + "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa7", + "isProxy": false + }, + { + "name": "FallbackRoutingHook", + "address": "0x886BB0f329781b98f98FDeb1ce7a8957F2d43B9F", + "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa7000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000006963480b05eb58f4d624b014ab92e9ad4d21df6d", + "isProxy": false + }, + { + "name": "PausableHook", + "address": "0xD0dca420feFda68537695A8D887080eeF4030AF7", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StorageGasOracle", + "address": "0x2F619Ac5122689180AeBB930ADccdae215d538a9", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "InterchainGasPaymaster", + "address": "0xEe08043cf22c80b27BF24d19999231dF4a3fC256", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0x145566181A18E23bB6a8A3eC6D87765542A7F754", + "constructorArguments": "000000000000000000000000ee08043cf22c80b27bf24d19999231df4a3fc256000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "expectedimplementation": "0xEe08043cf22c80b27BF24d19999231dF4a3fC256" + }, + { + "name": "ProtocolFee", + "address": "0x13E83ac41e696856B6996263501fB3225AD5E6F5", + "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "isProxy": false + }, + { + "name": "ValidatorAnnounce", + "address": "0x60B8d195f1b2EcaC26d54b95C69E6399cFD64b53", + "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa7", + "isProxy": false + } + ], + "b3": [ + { + "name": "ProxyAdmin", + "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "Mailbox", + "address": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", + "constructorArguments": "000000000000000000000000000000000000000000000000000000000000208d", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "expectedimplementation": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E" + }, + { + "name": "PausableIsm", + "address": "0x696df5e79C4f1bd5F8D587Ba8946361d9B029d4B", + "constructorArguments": "000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "isProxy": false + }, + { + "name": "MerkleTreeHook", + "address": "0x6963480b05EB58f4d624B014ab92e9aD4d21df6D", + "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa7", + "isProxy": false + }, + { + "name": "FallbackRoutingHook", + "address": "0x886BB0f329781b98f98FDeb1ce7a8957F2d43B9F", + "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa7000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000006963480b05eb58f4d624b014ab92e9ad4d21df6d", + "isProxy": false + }, + { + "name": "PausableHook", + "address": "0xD0dca420feFda68537695A8D887080eeF4030AF7", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StorageGasOracle", + "address": "0x2F619Ac5122689180AeBB930ADccdae215d538a9", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "InterchainGasPaymaster", + "address": "0xEe08043cf22c80b27BF24d19999231dF4a3fC256", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0x145566181A18E23bB6a8A3eC6D87765542A7F754", + "constructorArguments": "000000000000000000000000ee08043cf22c80b27bf24d19999231df4a3fc256000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "expectedimplementation": "0xEe08043cf22c80b27BF24d19999231dF4a3fC256" + }, + { + "name": "ProtocolFee", + "address": "0x13E83ac41e696856B6996263501fB3225AD5E6F5", + "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "isProxy": false + }, + { + "name": "ValidatorAnnounce", + "address": "0x60B8d195f1b2EcaC26d54b95C69E6399cFD64b53", + "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa7", + "isProxy": false + } + ], + "orderly": [ + { + "name": "ProxyAdmin", + "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "Mailbox", + "address": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", + "constructorArguments": "0000000000000000000000000000000000000000000000000000000000000123", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "expectedimplementation": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E" + }, + { + "name": "PausableIsm", + "address": "0x696df5e79C4f1bd5F8D587Ba8946361d9B029d4B", + "constructorArguments": "000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "isProxy": false + }, + { + "name": "MerkleTreeHook", + "address": "0x6963480b05EB58f4d624B014ab92e9aD4d21df6D", + "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa7", + "isProxy": false + }, + { + "name": "FallbackRoutingHook", + "address": "0x886BB0f329781b98f98FDeb1ce7a8957F2d43B9F", + "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa7000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000006963480b05eb58f4d624b014ab92e9ad4d21df6d", + "isProxy": false + }, + { + "name": "PausableHook", + "address": "0xD0dca420feFda68537695A8D887080eeF4030AF7", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StorageGasOracle", + "address": "0x2F619Ac5122689180AeBB930ADccdae215d538a9", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "InterchainGasPaymaster", + "address": "0xEe08043cf22c80b27BF24d19999231dF4a3fC256", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0x145566181A18E23bB6a8A3eC6D87765542A7F754", + "constructorArguments": "000000000000000000000000ee08043cf22c80b27bf24d19999231df4a3fc256000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "expectedimplementation": "0xEe08043cf22c80b27BF24d19999231dF4a3fC256" + }, + { + "name": "ProtocolFee", + "address": "0x13E83ac41e696856B6996263501fB3225AD5E6F5", + "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "isProxy": false + }, + { + "name": "ValidatorAnnounce", + "address": "0x60B8d195f1b2EcaC26d54b95C69E6399cFD64b53", + "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa7", + "isProxy": false + } + ], + "snaxchain": [ + { + "name": "ProxyAdmin", + "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "Mailbox", + "address": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", + "constructorArguments": "0000000000000000000000000000000000000000000000000000000000000890", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "expectedimplementation": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E" + }, + { + "name": "PausableIsm", + "address": "0x696df5e79C4f1bd5F8D587Ba8946361d9B029d4B", + "constructorArguments": "000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "isProxy": false + }, + { + "name": "MerkleTreeHook", + "address": "0x6963480b05EB58f4d624B014ab92e9aD4d21df6D", + "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa7", + "isProxy": false + }, + { + "name": "FallbackRoutingHook", + "address": "0x886BB0f329781b98f98FDeb1ce7a8957F2d43B9F", + "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa7000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000006963480b05eb58f4d624b014ab92e9ad4d21df6d", + "isProxy": false + }, + { + "name": "PausableHook", + "address": "0xD0dca420feFda68537695A8D887080eeF4030AF7", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StorageGasOracle", + "address": "0x2F619Ac5122689180AeBB930ADccdae215d538a9", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "InterchainGasPaymaster", + "address": "0xEe08043cf22c80b27BF24d19999231dF4a3fC256", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0x145566181A18E23bB6a8A3eC6D87765542A7F754", + "constructorArguments": "000000000000000000000000ee08043cf22c80b27bf24d19999231df4a3fc256000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "expectedimplementation": "0xEe08043cf22c80b27BF24d19999231dF4a3fC256" + }, + { + "name": "ProtocolFee", + "address": "0x13E83ac41e696856B6996263501fB3225AD5E6F5", + "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "isProxy": false + }, + { + "name": "ValidatorAnnounce", + "address": "0x60B8d195f1b2EcaC26d54b95C69E6399cFD64b53", + "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa7", + "isProxy": false + } + ], + "morph": [ + { + "name": "ProxyAdmin", + "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "Mailbox", + "address": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", + "constructorArguments": "0000000000000000000000000000000000000000000000000000000000000b02", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "expectedimplementation": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E" + }, + { + "name": "PausableIsm", + "address": "0x696df5e79C4f1bd5F8D587Ba8946361d9B029d4B", + "constructorArguments": "000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "isProxy": false + }, + { + "name": "MerkleTreeHook", + "address": "0x6963480b05EB58f4d624B014ab92e9aD4d21df6D", + "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa7", + "isProxy": false + }, + { + "name": "FallbackRoutingHook", + "address": "0x886BB0f329781b98f98FDeb1ce7a8957F2d43B9F", + "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa7000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000006963480b05eb58f4d624b014ab92e9ad4d21df6d", + "isProxy": false + }, + { + "name": "PausableHook", + "address": "0xD0dca420feFda68537695A8D887080eeF4030AF7", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StorageGasOracle", + "address": "0x2F619Ac5122689180AeBB930ADccdae215d538a9", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "InterchainGasPaymaster", + "address": "0xEe08043cf22c80b27BF24d19999231dF4a3fC256", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0x145566181A18E23bB6a8A3eC6D87765542A7F754", + "constructorArguments": "000000000000000000000000ee08043cf22c80b27bf24d19999231df4a3fc256000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "expectedimplementation": "0xEe08043cf22c80b27BF24d19999231dF4a3fC256" + }, + { + "name": "ProtocolFee", + "address": "0x13E83ac41e696856B6996263501fB3225AD5E6F5", + "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "isProxy": false + }, + { + "name": "ValidatorAnnounce", + "address": "0x60B8d195f1b2EcaC26d54b95C69E6399cFD64b53", + "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa7", + "isProxy": false + } + ], + "fantom": [ + { + "name": "ProxyAdmin", + "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "Mailbox", + "address": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", + "constructorArguments": "00000000000000000000000000000000000000000000000000000000000000fa", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "expectedimplementation": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E" + }, + { + "name": "PausableIsm", + "address": "0x696df5e79C4f1bd5F8D587Ba8946361d9B029d4B", + "constructorArguments": "000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "isProxy": false + }, + { + "name": "MerkleTreeHook", + "address": "0x6963480b05EB58f4d624B014ab92e9aD4d21df6D", + "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa7", + "isProxy": false + }, + { + "name": "FallbackRoutingHook", + "address": "0x886BB0f329781b98f98FDeb1ce7a8957F2d43B9F", + "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa7000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000006963480b05eb58f4d624b014ab92e9ad4d21df6d", + "isProxy": false + }, + { + "name": "PausableHook", + "address": "0xD0dca420feFda68537695A8D887080eeF4030AF7", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StorageGasOracle", + "address": "0x2F619Ac5122689180AeBB930ADccdae215d538a9", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "InterchainGasPaymaster", + "address": "0xEe08043cf22c80b27BF24d19999231dF4a3fC256", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0x145566181A18E23bB6a8A3eC6D87765542A7F754", + "constructorArguments": "000000000000000000000000ee08043cf22c80b27bf24d19999231df4a3fc256000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "expectedimplementation": "0xEe08043cf22c80b27BF24d19999231dF4a3fC256" + }, + { + "name": "ProtocolFee", + "address": "0x13E83ac41e696856B6996263501fB3225AD5E6F5", + "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "isProxy": false + }, + { + "name": "ValidatorAnnounce", + "address": "0x60B8d195f1b2EcaC26d54b95C69E6399cFD64b53", + "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa7", + "isProxy": false + } ] } diff --git a/typescript/infra/config/environments/mainnet3/funding.ts b/typescript/infra/config/environments/mainnet3/funding.ts index 89e34339a9..1c348376a5 100644 --- a/typescript/infra/config/environments/mainnet3/funding.ts +++ b/typescript/infra/config/environments/mainnet3/funding.ts @@ -10,7 +10,7 @@ export const keyFunderConfig: KeyFunderConfig< > = { docker: { repo: 'gcr.io/abacus-labs-dev/hyperlane-monorepo', - tag: '25ae727-20241025-165028', + tag: '2cde782-20241029-100534', }, // We're currently using the same deployer/key funder key as mainnet2. // To minimize nonce clobbering we offset the key funder cron @@ -28,10 +28,13 @@ export const keyFunderConfig: KeyFunderConfig< desiredBalancePerChain: { ancient8: '0.5', alephzeroevm: '100', + apechain: '50', arbitrum: '0.5', + arbitrumnova: '0.05', astar: '100', astarzkevm: '0.05', avalanche: '5', + b3: '0.05', base: '0.5', bitlayer: '0.002', blast: '0.2', @@ -47,13 +50,17 @@ export const keyFunderConfig: KeyFunderConfig< endurance: '20', ethereum: '0.5', everclear: '0.05', + fantom: '100', flare: '500', flow: '5', fraxtal: '0.2', fusemainnet: '20', gnosis: '5', + gravity: '500', + harmony: '500', immutablezkevm: '25', inevm: '3', + kaia: '250', kroma: '0.05', linea: '0.2', lisk: '0.05', @@ -68,8 +75,10 @@ export const keyFunderConfig: KeyFunderConfig< mode: '0.2', molten: '3', moonbeam: '5', + morph: '0.05', oortmainnet: '2000', optimism: '0.5', + orderly: '0.05', polygon: '20', polygonzkevm: '0.5', polynomial: '0.05', @@ -82,6 +91,7 @@ export const keyFunderConfig: KeyFunderConfig< scroll: '0.5', sei: '50', shibarium: '50', + snaxchain: '0.05', // ignore non-evm chains stride: '0', superposition: '0.05', @@ -91,8 +101,10 @@ export const keyFunderConfig: KeyFunderConfig< worldchain: '0.2', xai: '20', xlayer: '0.5', + zeronetwork: '0.05', zetachain: '20', zircuit: '0.02', + zksync: '0.05', zoramainnet: '0.2', // ignore non-evm chains injective: '0', diff --git a/typescript/infra/config/environments/mainnet3/gasPrices.json b/typescript/infra/config/environments/mainnet3/gasPrices.json index 7f61122c8d..4652bedc11 100644 --- a/typescript/infra/config/environments/mainnet3/gasPrices.json +++ b/typescript/infra/config/environments/mainnet3/gasPrices.json @@ -7,8 +7,16 @@ "amount": "40.0", "decimals": 9 }, + "apechain": { + "amount": "25.42069", + "decimals": 9 + }, "arbitrum": { - "amount": "0.017537", + "amount": "0.032084", + "decimals": 9 + }, + "arbitrumnova": { + "amount": "0.01", "decimals": 9 }, "astar": { @@ -16,15 +24,19 @@ "decimals": 9 }, "astarzkevm": { - "amount": "0.399", + "amount": "0.0696", "decimals": 9 }, "avalanche": { "amount": "25.0", "decimals": 9 }, + "b3": { + "amount": "0.001000252", + "decimals": 9 + }, "base": { - "amount": "0.015396226", + "amount": "0.025135767", "decimals": 9 }, "bitlayer": { @@ -32,7 +44,7 @@ "decimals": 9 }, "blast": { - "amount": "0.005712307", + "amount": "0.004639802", "decimals": 9 }, "bob": { @@ -76,19 +88,23 @@ "decimals": 1 }, "endurance": { - "amount": "3.0756015", + "amount": "1.500000007", "decimals": 9 }, "ethereum": { - "amount": "14.852716956", + "amount": "9.379005838", "decimals": 9 }, "everclear": { "amount": "0.1", "decimals": 9 }, + "fantom": { + "amount": "9.871668079", + "decimals": 9 + }, "flare": { - "amount": "49.455765643", + "amount": "26.179282546", "decimals": 9 }, "flow": { @@ -104,11 +120,19 @@ "decimals": 9 }, "gnosis": { - "amount": "1.500000007", + "amount": "1.430000001", + "decimals": 9 + }, + "gravity": { + "amount": "1800.0", + "decimals": 9 + }, + "harmony": { + "amount": "100.0", "decimals": 9 }, "immutablezkevm": { - "amount": "10.1", + "amount": "11.000000049", "decimals": 9 }, "inevm": { @@ -119,12 +143,16 @@ "amount": "700000000", "decimals": 1 }, + "kaia": { + "amount": "27.5", + "decimals": 9 + }, "kroma": { "amount": "0.001000252", "decimals": 9 }, "linea": { - "amount": "0.243", + "amount": "0.312253183", "decimals": 9 }, "lisk": { @@ -132,7 +160,7 @@ "decimals": 9 }, "lukso": { - "amount": "0.82770224", + "amount": "0.557453646", "decimals": 9 }, "lumia": { @@ -140,7 +168,7 @@ "decimals": 9 }, "mantapacific": { - "amount": "0.00129795", + "amount": "0.001001418", "decimals": 9 }, "mantle": { @@ -156,7 +184,7 @@ "decimals": 9 }, "metis": { - "amount": "1.278943587", + "amount": "1.077971462", "decimals": 9 }, "mint": { @@ -164,7 +192,7 @@ "decimals": 9 }, "mode": { - "amount": "0.001000313", + "amount": "0.001000265", "decimals": 9 }, "molten": { @@ -175,6 +203,10 @@ "amount": "125.0", "decimals": 9 }, + "morph": { + "amount": "0.0041472", + "decimals": 9 + }, "neutron": { "amount": "0.0053", "decimals": 1 @@ -184,7 +216,11 @@ "decimals": 9 }, "optimism": { - "amount": "0.02163419", + "amount": "0.001001064", + "decimals": 9 + }, + "orderly": { + "amount": "0.00100025", "decimals": 9 }, "osmosis": { @@ -192,11 +228,11 @@ "decimals": 1 }, "polygon": { - "amount": "61.134765243", + "amount": "100", "decimals": 9 }, "polygonzkevm": { - "amount": "0.726", + "amount": "0.5", "decimals": 9 }, "polynomial": { @@ -208,11 +244,11 @@ "decimals": 9 }, "rari": { - "amount": "0.03", + "amount": "0.15", "decimals": 9 }, "real": { - "amount": "0.022", + "amount": "0.03", "decimals": 9 }, "redstone": { @@ -228,7 +264,7 @@ "decimals": 9 }, "scroll": { - "amount": "0.048755492", + "amount": "0.041113999", "decimals": 9 }, "sei": { @@ -236,7 +272,11 @@ "decimals": 9 }, "shibarium": { - "amount": "39.319461243", + "amount": "61.627947201", + "decimals": 9 + }, + "snaxchain": { + "amount": "0.001000252", "decimals": 9 }, "solanamainnet": { @@ -252,7 +292,7 @@ "decimals": 9 }, "taiko": { - "amount": "0.050000001", + "amount": "0.11100569", "decimals": 9 }, "tangle": { @@ -264,15 +304,19 @@ "decimals": 9 }, "worldchain": { - "amount": "0.001000251", + "amount": "0.001000298", "decimals": 9 }, "xai": { - "amount": "0.1", + "amount": "0.10081", "decimals": 9 }, "xlayer": { - "amount": "14.54921875", + "amount": "5.882081", + "decimals": 9 + }, + "zeronetwork": { + "amount": "0.04525", "decimals": 9 }, "zetachain": { @@ -283,8 +327,12 @@ "amount": "0.001000253", "decimals": 9 }, + "zksync": { + "amount": "0.04525", + "decimals": 9 + }, "zoramainnet": { - "amount": "0.001000274", + "amount": "0.001000255", "decimals": 9 } } diff --git a/typescript/infra/config/environments/mainnet3/ism/verification.json b/typescript/infra/config/environments/mainnet3/ism/verification.json index 9655055391..b291cf0e35 100644 --- a/typescript/infra/config/environments/mainnet3/ism/verification.json +++ b/typescript/infra/config/environments/mainnet3/ism/verification.json @@ -5798,5 +5798,865 @@ "constructorArguments": "", "isProxy": true } + ], + "gravity": [ + { + "name": "StaticMerkleRootMultisigIsmFactory", + "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0x4725F7b8037513915aAf6D6CBDE2920E28540dDc", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdMultisigIsmFactory", + "address": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0xAF03386044373E2fe26C5b1dCedF5a7e854a7a3F", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationIsmFactory", + "address": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticAggregationIsm", + "address": "0x882CD0C5D50b6dD74b36Da4BDb059507fddEDdf2", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationHookFactory", + "address": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticAggregationHook", + "address": "0x19930232E9aFC4f4F09d09fe2375680fAc2100D0", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "DomainRoutingIsmFactory", + "address": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "DomaingRoutingIsm", + "address": "0x12Ed1BbA182CbC63692F813651BD493B7445C874", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMerkleRootWeightedMultisigIsmFactory", + "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticMerkleRootWeightedMultisigIsm", + "address": "0x3b9f24fD2ecfed0d3A88fa7f0E4e5747671981D7", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdWeightedMultisigIsmFactory", + "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticMessageIdWeightedMultisigIsm", + "address": "0x71DCcD21B912F7d4f636af0C9eA5DC0C10617354", + "constructorArguments": "", + "isProxy": true + } + ], + "apechain": [ + { + "name": "StaticMerkleRootMultisigIsmFactory", + "address": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0x882CD0C5D50b6dD74b36Da4BDb059507fddEDdf2", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdMultisigIsmFactory", + "address": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0x19930232E9aFC4f4F09d09fe2375680fAc2100D0", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationIsmFactory", + "address": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticAggregationIsm", + "address": "0x12Ed1BbA182CbC63692F813651BD493B7445C874", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationHookFactory", + "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticAggregationHook", + "address": "0x3b9f24fD2ecfed0d3A88fa7f0E4e5747671981D7", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "DomainRoutingIsmFactory", + "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "DomaingRoutingIsm", + "address": "0x71DCcD21B912F7d4f636af0C9eA5DC0C10617354", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMerkleRootWeightedMultisigIsmFactory", + "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticMerkleRootWeightedMultisigIsm", + "address": "0x7f51A658837A315134A97ff8B586d71B726B7e61", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdWeightedMultisigIsmFactory", + "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticMessageIdWeightedMultisigIsm", + "address": "0xDFF18Bf286c9cDd0fC653a28616460Cf7443F8EF", + "constructorArguments": "", + "isProxy": true + } + ], + "arbitrumnova": [ + { + "name": "StaticMerkleRootMultisigIsmFactory", + "address": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0xAF03386044373E2fe26C5b1dCedF5a7e854a7a3F", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdMultisigIsmFactory", + "address": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0x882CD0C5D50b6dD74b36Da4BDb059507fddEDdf2", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationIsmFactory", + "address": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticAggregationIsm", + "address": "0x19930232E9aFC4f4F09d09fe2375680fAc2100D0", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationHookFactory", + "address": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticAggregationHook", + "address": "0x12Ed1BbA182CbC63692F813651BD493B7445C874", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "DomainRoutingIsmFactory", + "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "DomaingRoutingIsm", + "address": "0x3b9f24fD2ecfed0d3A88fa7f0E4e5747671981D7", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMerkleRootWeightedMultisigIsmFactory", + "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticMerkleRootWeightedMultisigIsm", + "address": "0x71DCcD21B912F7d4f636af0C9eA5DC0C10617354", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdWeightedMultisigIsmFactory", + "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticMessageIdWeightedMultisigIsm", + "address": "0x7f51A658837A315134A97ff8B586d71B726B7e61", + "constructorArguments": "", + "isProxy": true + } + ], + "harmony": [ + { + "name": "StaticMerkleRootMultisigIsmFactory", + "address": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0xAF03386044373E2fe26C5b1dCedF5a7e854a7a3F", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdMultisigIsmFactory", + "address": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0x882CD0C5D50b6dD74b36Da4BDb059507fddEDdf2", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationIsmFactory", + "address": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticAggregationIsm", + "address": "0x19930232E9aFC4f4F09d09fe2375680fAc2100D0", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationHookFactory", + "address": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticAggregationHook", + "address": "0x12Ed1BbA182CbC63692F813651BD493B7445C874", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "DomainRoutingIsmFactory", + "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "DomaingRoutingIsm", + "address": "0x3b9f24fD2ecfed0d3A88fa7f0E4e5747671981D7", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMerkleRootWeightedMultisigIsmFactory", + "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticMerkleRootWeightedMultisigIsm", + "address": "0x71DCcD21B912F7d4f636af0C9eA5DC0C10617354", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdWeightedMultisigIsmFactory", + "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticMessageIdWeightedMultisigIsm", + "address": "0x7f51A658837A315134A97ff8B586d71B726B7e61", + "constructorArguments": "", + "isProxy": true + } + ], + "kaia": [ + { + "name": "StaticMerkleRootMultisigIsmFactory", + "address": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0xAF03386044373E2fe26C5b1dCedF5a7e854a7a3F", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdMultisigIsmFactory", + "address": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0x882CD0C5D50b6dD74b36Da4BDb059507fddEDdf2", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationIsmFactory", + "address": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticAggregationIsm", + "address": "0x19930232E9aFC4f4F09d09fe2375680fAc2100D0", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationHookFactory", + "address": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticAggregationHook", + "address": "0x12Ed1BbA182CbC63692F813651BD493B7445C874", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "DomainRoutingIsmFactory", + "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "DomaingRoutingIsm", + "address": "0x3b9f24fD2ecfed0d3A88fa7f0E4e5747671981D7", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMerkleRootWeightedMultisigIsmFactory", + "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticMerkleRootWeightedMultisigIsm", + "address": "0x71DCcD21B912F7d4f636af0C9eA5DC0C10617354", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdWeightedMultisigIsmFactory", + "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticMessageIdWeightedMultisigIsm", + "address": "0x7f51A658837A315134A97ff8B586d71B726B7e61", + "constructorArguments": "", + "isProxy": true + } + ], + "b3": [ + { + "name": "StaticMerkleRootMultisigIsmFactory", + "address": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0xAF03386044373E2fe26C5b1dCedF5a7e854a7a3F", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdMultisigIsmFactory", + "address": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0x882CD0C5D50b6dD74b36Da4BDb059507fddEDdf2", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationIsmFactory", + "address": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticAggregationIsm", + "address": "0x19930232E9aFC4f4F09d09fe2375680fAc2100D0", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationHookFactory", + "address": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticAggregationHook", + "address": "0x12Ed1BbA182CbC63692F813651BD493B7445C874", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "DomainRoutingIsmFactory", + "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "DomaingRoutingIsm", + "address": "0x3b9f24fD2ecfed0d3A88fa7f0E4e5747671981D7", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMerkleRootWeightedMultisigIsmFactory", + "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticMerkleRootWeightedMultisigIsm", + "address": "0x71DCcD21B912F7d4f636af0C9eA5DC0C10617354", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdWeightedMultisigIsmFactory", + "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticMessageIdWeightedMultisigIsm", + "address": "0x7f51A658837A315134A97ff8B586d71B726B7e61", + "constructorArguments": "", + "isProxy": true + } + ], + "fantom": [ + { + "name": "StaticMerkleRootMultisigIsmFactory", + "address": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0xAF03386044373E2fe26C5b1dCedF5a7e854a7a3F", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdMultisigIsmFactory", + "address": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0x882CD0C5D50b6dD74b36Da4BDb059507fddEDdf2", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationIsmFactory", + "address": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticAggregationIsm", + "address": "0x19930232E9aFC4f4F09d09fe2375680fAc2100D0", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationHookFactory", + "address": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticAggregationHook", + "address": "0x12Ed1BbA182CbC63692F813651BD493B7445C874", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "DomainRoutingIsmFactory", + "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "DomaingRoutingIsm", + "address": "0x3b9f24fD2ecfed0d3A88fa7f0E4e5747671981D7", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMerkleRootWeightedMultisigIsmFactory", + "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticMerkleRootWeightedMultisigIsm", + "address": "0x71DCcD21B912F7d4f636af0C9eA5DC0C10617354", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdWeightedMultisigIsmFactory", + "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticMessageIdWeightedMultisigIsm", + "address": "0x7f51A658837A315134A97ff8B586d71B726B7e61", + "constructorArguments": "", + "isProxy": true + } + ], + "morph": [ + { + "name": "StaticMerkleRootMultisigIsmFactory", + "address": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0xAF03386044373E2fe26C5b1dCedF5a7e854a7a3F", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdMultisigIsmFactory", + "address": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0x882CD0C5D50b6dD74b36Da4BDb059507fddEDdf2", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationIsmFactory", + "address": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticAggregationIsm", + "address": "0x19930232E9aFC4f4F09d09fe2375680fAc2100D0", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationHookFactory", + "address": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticAggregationHook", + "address": "0x12Ed1BbA182CbC63692F813651BD493B7445C874", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "DomainRoutingIsmFactory", + "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "DomaingRoutingIsm", + "address": "0x3b9f24fD2ecfed0d3A88fa7f0E4e5747671981D7", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMerkleRootWeightedMultisigIsmFactory", + "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticMerkleRootWeightedMultisigIsm", + "address": "0x71DCcD21B912F7d4f636af0C9eA5DC0C10617354", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdWeightedMultisigIsmFactory", + "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticMessageIdWeightedMultisigIsm", + "address": "0x7f51A658837A315134A97ff8B586d71B726B7e61", + "constructorArguments": "", + "isProxy": true + } + ], + "orderly": [ + { + "name": "StaticMerkleRootMultisigIsmFactory", + "address": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0xAF03386044373E2fe26C5b1dCedF5a7e854a7a3F", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdMultisigIsmFactory", + "address": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0x882CD0C5D50b6dD74b36Da4BDb059507fddEDdf2", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationIsmFactory", + "address": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticAggregationIsm", + "address": "0x19930232E9aFC4f4F09d09fe2375680fAc2100D0", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationHookFactory", + "address": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticAggregationHook", + "address": "0x12Ed1BbA182CbC63692F813651BD493B7445C874", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "DomainRoutingIsmFactory", + "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "DomaingRoutingIsm", + "address": "0x3b9f24fD2ecfed0d3A88fa7f0E4e5747671981D7", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMerkleRootWeightedMultisigIsmFactory", + "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticMerkleRootWeightedMultisigIsm", + "address": "0x71DCcD21B912F7d4f636af0C9eA5DC0C10617354", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdWeightedMultisigIsmFactory", + "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticMessageIdWeightedMultisigIsm", + "address": "0x7f51A658837A315134A97ff8B586d71B726B7e61", + "constructorArguments": "", + "isProxy": true + } + ], + "snaxchain": [ + { + "name": "StaticMerkleRootMultisigIsmFactory", + "address": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0xAF03386044373E2fe26C5b1dCedF5a7e854a7a3F", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdMultisigIsmFactory", + "address": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0x882CD0C5D50b6dD74b36Da4BDb059507fddEDdf2", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationIsmFactory", + "address": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticAggregationIsm", + "address": "0x19930232E9aFC4f4F09d09fe2375680fAc2100D0", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationHookFactory", + "address": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticAggregationHook", + "address": "0x12Ed1BbA182CbC63692F813651BD493B7445C874", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "DomainRoutingIsmFactory", + "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "DomaingRoutingIsm", + "address": "0x3b9f24fD2ecfed0d3A88fa7f0E4e5747671981D7", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMerkleRootWeightedMultisigIsmFactory", + "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticMerkleRootWeightedMultisigIsm", + "address": "0x71DCcD21B912F7d4f636af0C9eA5DC0C10617354", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdWeightedMultisigIsmFactory", + "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticMessageIdWeightedMultisigIsm", + "address": "0x7f51A658837A315134A97ff8B586d71B726B7e61", + "constructorArguments": "", + "isProxy": true + } ] } diff --git a/typescript/infra/config/environments/mainnet3/middleware/accounts/verification.json b/typescript/infra/config/environments/mainnet3/middleware/accounts/verification.json index 62bda474ae..773a6e7817 100644 --- a/typescript/infra/config/environments/mainnet3/middleware/accounts/verification.json +++ b/typescript/infra/config/environments/mainnet3/middleware/accounts/verification.json @@ -1599,5 +1599,215 @@ "isProxy": true, "expectedimplementation": "0x3E12271EbD523d0886D0D51A4FF8D8e046CF2E1D" } + ], + "gravity": [ + { + "name": "InterchainAccountIsm", + "address": "0x783EC5e105234a570eB90f314284E5dBe53bdd90", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", + "isProxy": false + }, + { + "name": "InterchainAccountRouter", + "address": "0xFB9e40D811Cea562cc8a322b029eF2BDcC3ef6ed", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0xc5D6aCaafBCcEC6D7fD7d92F4509befce641c563", + "constructorArguments": "000000000000000000000000fb9e40d811cea562cc8a322b029ef2bdcc3ef6ed0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000783ec5e105234a570eb90f314284e5dbe53bdd90000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "expectedimplementation": "0xFB9e40D811Cea562cc8a322b029eF2BDcC3ef6ed" + } + ], + "apechain": [ + { + "name": "InterchainAccountIsm", + "address": "0x6119B76720CcfeB3D256EC1b91218EEfFD6756E1", + "constructorArguments": "0000000000000000000000007f50c5776722630a0024fae05fde8b47571d7b39", + "isProxy": false + }, + { + "name": "InterchainAccountRouter", + "address": "0xc5D6aCaafBCcEC6D7fD7d92F4509befce641c563", + "constructorArguments": "0000000000000000000000007f50c5776722630a0024fae05fde8b47571d7b39", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0x9eaaC366BFD70430cFee6E70265fefFf1CfC9E47", + "constructorArguments": "000000000000000000000000c5d6acaafbccec6d7fd7d92f4509befce641c5630000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000006119b76720ccfeb3d256ec1b91218eeffd6756e1000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "expectedimplementation": "0xc5D6aCaafBCcEC6D7fD7d92F4509befce641c563" + } + ], + "arbitrumnova": [ + { + "name": "InterchainAccountIsm", + "address": "0xFB9e40D811Cea562cc8a322b029eF2BDcC3ef6ed", + "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa7", + "isProxy": false + }, + { + "name": "InterchainAccountRouter", + "address": "0x6119B76720CcfeB3D256EC1b91218EEfFD6756E1", + "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa7", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0xeE8C0E1EeBfFCC451a013336386eA53E42a44451", + "constructorArguments": "0000000000000000000000006119b76720ccfeb3d256ec1b91218eeffd6756e1000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fb9e40d811cea562cc8a322b029ef2bdcc3ef6ed000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "expectedimplementation": "0x6119B76720CcfeB3D256EC1b91218EEfFD6756E1" + } + ], + "harmony": [ + { + "name": "InterchainAccountIsm", + "address": "0xFB9e40D811Cea562cc8a322b029eF2BDcC3ef6ed", + "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa7", + "isProxy": false + }, + { + "name": "InterchainAccountRouter", + "address": "0x6119B76720CcfeB3D256EC1b91218EEfFD6756E1", + "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa7", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0xeE8C0E1EeBfFCC451a013336386eA53E42a44451", + "constructorArguments": "0000000000000000000000006119b76720ccfeb3d256ec1b91218eeffd6756e1000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fb9e40d811cea562cc8a322b029ef2bdcc3ef6ed000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "expectedimplementation": "0x6119B76720CcfeB3D256EC1b91218EEfFD6756E1" + } + ], + "kaia": [ + { + "name": "InterchainAccountIsm", + "address": "0xFB9e40D811Cea562cc8a322b029eF2BDcC3ef6ed", + "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa7", + "isProxy": false + }, + { + "name": "InterchainAccountRouter", + "address": "0x6119B76720CcfeB3D256EC1b91218EEfFD6756E1", + "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa7", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0xeE8C0E1EeBfFCC451a013336386eA53E42a44451", + "constructorArguments": "0000000000000000000000006119b76720ccfeb3d256ec1b91218eeffd6756e1000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fb9e40d811cea562cc8a322b029ef2bdcc3ef6ed000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "expectedimplementation": "0x6119B76720CcfeB3D256EC1b91218EEfFD6756E1" + } + ], + "b3": [ + { + "name": "InterchainAccountIsm", + "address": "0xFB9e40D811Cea562cc8a322b029eF2BDcC3ef6ed", + "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa7", + "isProxy": false + }, + { + "name": "InterchainAccountRouter", + "address": "0x6119B76720CcfeB3D256EC1b91218EEfFD6756E1", + "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa7", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0xeE8C0E1EeBfFCC451a013336386eA53E42a44451", + "constructorArguments": "0000000000000000000000006119b76720ccfeb3d256ec1b91218eeffd6756e1000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fb9e40d811cea562cc8a322b029ef2bdcc3ef6ed000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "expectedimplementation": "0x6119B76720CcfeB3D256EC1b91218EEfFD6756E1" + } + ], + "orderly": [ + { + "name": "InterchainAccountIsm", + "address": "0xFB9e40D811Cea562cc8a322b029eF2BDcC3ef6ed", + "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa7", + "isProxy": false + }, + { + "name": "InterchainAccountRouter", + "address": "0x6119B76720CcfeB3D256EC1b91218EEfFD6756E1", + "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa7", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0xeE8C0E1EeBfFCC451a013336386eA53E42a44451", + "constructorArguments": "0000000000000000000000006119b76720ccfeb3d256ec1b91218eeffd6756e1000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fb9e40d811cea562cc8a322b029ef2bdcc3ef6ed000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "expectedimplementation": "0x6119B76720CcfeB3D256EC1b91218EEfFD6756E1" + } + ], + "snaxchain": [ + { + "name": "InterchainAccountIsm", + "address": "0xFB9e40D811Cea562cc8a322b029eF2BDcC3ef6ed", + "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa7", + "isProxy": false + }, + { + "name": "InterchainAccountRouter", + "address": "0x6119B76720CcfeB3D256EC1b91218EEfFD6756E1", + "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa7", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0xeE8C0E1EeBfFCC451a013336386eA53E42a44451", + "constructorArguments": "0000000000000000000000006119b76720ccfeb3d256ec1b91218eeffd6756e1000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fb9e40d811cea562cc8a322b029ef2bdcc3ef6ed000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "expectedimplementation": "0x6119B76720CcfeB3D256EC1b91218EEfFD6756E1" + } + ], + "fantom": [ + { + "name": "InterchainAccountIsm", + "address": "0xFB9e40D811Cea562cc8a322b029eF2BDcC3ef6ed", + "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa7", + "isProxy": false + }, + { + "name": "InterchainAccountRouter", + "address": "0x6119B76720CcfeB3D256EC1b91218EEfFD6756E1", + "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa7", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0xeE8C0E1EeBfFCC451a013336386eA53E42a44451", + "constructorArguments": "0000000000000000000000006119b76720ccfeb3d256ec1b91218eeffd6756e1000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fb9e40d811cea562cc8a322b029ef2bdcc3ef6ed000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "expectedimplementation": "0x6119B76720CcfeB3D256EC1b91218EEfFD6756E1" + } + ], + "morph": [ + { + "name": "InterchainAccountIsm", + "address": "0xFB9e40D811Cea562cc8a322b029eF2BDcC3ef6ed", + "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa7", + "isProxy": false + }, + { + "name": "InterchainAccountRouter", + "address": "0x6119B76720CcfeB3D256EC1b91218EEfFD6756E1", + "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa7", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0xeE8C0E1EeBfFCC451a013336386eA53E42a44451", + "constructorArguments": "0000000000000000000000006119b76720ccfeb3d256ec1b91218eeffd6756e1000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fb9e40d811cea562cc8a322b029ef2bdcc3ef6ed000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "expectedimplementation": "0x6119B76720CcfeB3D256EC1b91218EEfFD6756E1" + } ] } diff --git a/typescript/infra/config/environments/mainnet3/supportedChainNames.ts b/typescript/infra/config/environments/mainnet3/supportedChainNames.ts index 3e59572411..39957bea00 100644 --- a/typescript/infra/config/environments/mainnet3/supportedChainNames.ts +++ b/typescript/infra/config/environments/mainnet3/supportedChainNames.ts @@ -3,10 +3,13 @@ export const mainnet3SupportedChainNames = [ 'ancient8', 'alephzeroevm', + 'apechain', 'arbitrum', + 'arbitrumnova', 'astar', 'astarzkevm', 'avalanche', + 'b3', 'base', 'bitlayer', 'blast', @@ -23,14 +26,18 @@ export const mainnet3SupportedChainNames = [ 'endurance', 'ethereum', 'everclear', + 'fantom', 'flare', 'flow', 'fraxtal', 'fusemainnet', 'gnosis', + 'gravity', + 'harmony', 'immutablezkevm', 'inevm', 'injective', + 'kaia', 'kroma', 'linea', 'lisk', @@ -45,9 +52,11 @@ export const mainnet3SupportedChainNames = [ 'mode', 'molten', 'moonbeam', + 'morph', 'neutron', 'oortmainnet', 'optimism', + 'orderly', 'osmosis', 'polygon', 'polygonzkevm', @@ -61,6 +70,7 @@ export const mainnet3SupportedChainNames = [ 'scroll', 'sei', 'shibarium', + 'snaxchain', 'solanamainnet', 'stride', 'superposition', @@ -70,8 +80,10 @@ export const mainnet3SupportedChainNames = [ 'worldchain', 'xai', 'xlayer', + 'zeronetwork', 'zetachain', 'zircuit', + 'zksync', 'zoramainnet', ] as const; diff --git a/typescript/infra/config/environments/mainnet3/tokenPrices.json b/typescript/infra/config/environments/mainnet3/tokenPrices.json index 86c2ed731b..abe764bb71 100644 --- a/typescript/infra/config/environments/mainnet3/tokenPrices.json +++ b/typescript/infra/config/environments/mainnet3/tokenPrices.json @@ -1,74 +1,86 @@ { - "ancient8": "2629.74", - "alephzeroevm": "0.381786", - "arbitrum": "2629.74", - "astar": "0.061114", - "astarzkevm": "2629.74", - "avalanche": "27.96", - "base": "2629.74", - "bitlayer": "67813", - "blast": "2629.74", - "bob": "2629.74", - "bsc": "597.89", - "celo": "0.817141", - "cheesechain": "0.00556724", - "chiliz": "0.079288", - "coredao": "0.98348", - "cyber": "2629.74", - "degenchain": "0.00882961", - "dogechain": "0.126177", - "eclipsemainnet": "2629.74", - "endurance": "2.16", - "ethereum": "2629.74", - "everclear": "2629.74", - "flare": "0.01493582", - "flow": "0.558323", - "fraxtal": "2629.35", - "fusemainnet": "0.02901498", - "gnosis": "0.997404", - "immutablezkevm": "1.54", - "inevm": "21.05", - "injective": "21.05", - "kroma": "2629.74", - "linea": "2629.74", - "lisk": "2629.74", - "lukso": "1.47", - "lumia": "0.969511", - "mantapacific": "2629.74", - "mantle": "0.636484", - "merlin": "67781", - "metall2": "2629.74", - "metis": "45.78", - "mint": "2629.74", - "mode": "2629.74", - "molten": "0.436605", - "moonbeam": "0.169406", - "neutron": "0.408859", - "oortmainnet": "0.114304", - "optimism": "2629.74", - "osmosis": "0.558566", - "polygon": "0.371646", - "polygonzkevm": "2629.74", - "polynomial": "2629.74", - "proofofplay": "2629.74", - "rari": "2629.74", + "ancient8": "2509.23", + "alephzeroevm": "0.374106", + "apechain": "1.17", + "arbitrum": "2509.23", + "arbitrumnova": "2509.23", + "astar": "0.056948", + "astarzkevm": "2509.23", + "avalanche": "25.94", + "b3": "2509.23", + "base": "2509.23", + "bitlayer": "67372", + "blast": "2509.23", + "bob": "2509.23", + "bsc": "587.93", + "celo": "0.687006", + "cheesechain": "0.00295261", + "chiliz": "0.065082", + "coredao": "0.9041", + "cyber": "2509.23", + "degenchain": "0.00694026", + "dogechain": "0.135405", + "eclipsemainnet": "2509.23", + "endurance": "2.14", + "ethereum": "2509.23", + "everclear": "2509.23", + "fantom": "0.668043", + "flare": "0.01413977", + "flow": "0.537723", + "fraxtal": "2506.4", + "fusemainnet": "0.02708378", + "gnosis": "1", + "gravity": "0.03113181", + "harmony": "0.012995", + "immutablezkevm": "1.42", + "inevm": "19.75", + "injective": "19.75", + "kaia": "1", + "kroma": "2509.23", + "linea": "2509.23", + "lisk": "2509.23", + "lukso": "1.38", + "lumia": "1.14", + "mantapacific": "2509.23", + "mantle": "0.591459", + "merlin": "67402", + "metall2": "2509.23", + "metis": "41.48", + "mint": "2509.23", + "mode": "2509.23", + "molten": "0.202455", + "moonbeam": "0.163232", + "morph": "2509.23", + "neutron": "0.383777", + "oortmainnet": "0.109681", + "optimism": "2509.23", + "orderly": "2509.23", + "osmosis": "0.483881", + "polygon": "0.340093", + "polygonzkevm": "2509.23", + "polynomial": "2509.23", + "proofofplay": "2509.23", + "rari": "2509.23", "real": "1", - "redstone": "2629.74", - "rootstock": "67219", - "sanko": "70.7", - "scroll": "2629.74", - "sei": "0.447635", - "shibarium": "0.410927", - "solanamainnet": "155.35", - "stride": "0.840153", - "superposition": "2629.74", - "taiko": "2629.74", + "redstone": "2509.23", + "rootstock": "67025", + "sanko": "47.83", + "scroll": "2509.23", + "sei": "0.393025", + "shibarium": "0.383601", + "snaxchain": "2509.23", + "solanamainnet": "168.99", + "stride": "0.731952", + "superposition": "2509.23", + "taiko": "2509.23", "tangle": "1", - "viction": "0.369839", - "worldchain": "2629.74", - "xai": "0.216438", - "xlayer": "41.56", - "zetachain": "0.617959", - "zircuit": "2629.74", - "zoramainnet": "2629.74" + "viction": "0.348004", + "worldchain": "2509.23", + "xai": "0.20891", + "xlayer": "39.32", + "zeronetwork": "2509.23", + "zetachain": "0.661744", + "zircuit": "2509.23", + "zksync": "2509.23", + "zoramainnet": "2509.23" } diff --git a/typescript/infra/config/environments/mainnet3/validators.ts b/typescript/infra/config/environments/mainnet3/validators.ts index 0c09047449..c3747ac2a7 100644 --- a/typescript/infra/config/environments/mainnet3/validators.ts +++ b/typescript/infra/config/environments/mainnet3/validators.ts @@ -998,5 +998,127 @@ export const validatorChainConfig = ( 'flow', ), }, + + zeronetwork: { + interval: 5, + reorgPeriod: getReorgPeriod('zeronetwork'), + validators: validatorsConfig( + { + [Contexts.Hyperlane]: ['0x1bd9e3f8a90ea1a13b0f2838a1858046368aad87'], + }, + 'zeronetwork', + ), + }, + zksync: { + interval: 5, + reorgPeriod: getReorgPeriod('zksync'), + validators: validatorsConfig( + { + [Contexts.Hyperlane]: ['0xadd1d39ce7a687e32255ac457cf99a6d8c5b5d1a'], + }, + 'zksync', + ), + }, + + apechain: { + interval: 5, + reorgPeriod: getReorgPeriod('apechain'), + validators: validatorsConfig( + { + [Contexts.Hyperlane]: ['0x773d7fe6ffb1ba4de814c28044ff9a2d83a48221'], + }, + 'apechain', + ), + }, + arbitrumnova: { + interval: 5, + reorgPeriod: getReorgPeriod('arbitrumnova'), + validators: validatorsConfig( + { + [Contexts.Hyperlane]: ['0xd2a5e9123308d187383c87053811a2c21bd8af1f'], + }, + 'arbitrumnova', + ), + }, + b3: { + interval: 5, + reorgPeriod: getReorgPeriod('b3'), + validators: validatorsConfig( + { + [Contexts.Hyperlane]: ['0xd77b516730a836fc41934e7d5864e72c165b934e'], + }, + 'b3', + ), + }, + fantom: { + interval: 5, + reorgPeriod: getReorgPeriod('fantom'), + validators: validatorsConfig( + { + [Contexts.Hyperlane]: ['0xa779572028e634e16f26af5dfd4fa685f619457d'], + }, + 'fantom', + ), + }, + gravity: { + interval: 5, + reorgPeriod: getReorgPeriod('gravity'), + validators: validatorsConfig( + { + [Contexts.Hyperlane]: ['0x23d549bf757a02a6f6068e9363196ecd958c974e'], + }, + 'gravity', + ), + }, + harmony: { + interval: 5, + reorgPeriod: getReorgPeriod('harmony'), + validators: validatorsConfig( + { + [Contexts.Hyperlane]: ['0xd677803a67651974b1c264171b5d7ca8838db8d5'], + }, + 'harmony', + ), + }, + kaia: { + interval: 5, + reorgPeriod: getReorgPeriod('kaia'), + validators: validatorsConfig( + { + [Contexts.Hyperlane]: ['0x9de0b3abb221d19719882fa4d61f769fdc2be9a4'], + }, + 'kaia', + ), + }, + morph: { + interval: 5, + reorgPeriod: getReorgPeriod('morph'), + validators: validatorsConfig( + { + [Contexts.Hyperlane]: ['0x4884535f393151ec419add872100d352f71af380'], + }, + 'morph', + ), + }, + orderly: { + interval: 5, + reorgPeriod: getReorgPeriod('orderly'), + validators: validatorsConfig( + { + [Contexts.Hyperlane]: ['0xec3dc91f9fa2ad35edf5842aa764d5573b778bb6'], + }, + 'orderly', + ), + }, + snaxchain: { + interval: 5, + reorgPeriod: getReorgPeriod('snaxchain'), + validators: validatorsConfig( + { + [Contexts.Hyperlane]: ['0x2c25829ae32a772d2a49f6c4b34f8b01fd03ef9e'], + }, + 'snaxchain', + ), + }, }; }; diff --git a/typescript/infra/scripts/deploy.ts b/typescript/infra/scripts/deploy.ts index 111d85dbfb..2c8d3ff90a 100644 --- a/typescript/infra/scripts/deploy.ts +++ b/typescript/infra/scripts/deploy.ts @@ -71,6 +71,10 @@ async function main() { ).argv; const envConfig = getEnvironmentConfig(environment); + // TODO: remove once zksync PR is merged into main + delete envConfig.core.zksync; + delete envConfig.core.zeronetwork; + let multiProvider = await envConfig.getMultiProvider( context, Role.Deployer, diff --git a/typescript/sdk/src/consts/multisigIsm.ts b/typescript/sdk/src/consts/multisigIsm.ts index 5184a4b9e5..27478ed9e6 100644 --- a/typescript/sdk/src/consts/multisigIsm.ts +++ b/typescript/sdk/src/consts/multisigIsm.ts @@ -30,6 +30,11 @@ export const defaultMultisigConfigs: ChainMap = { ], }, + apechain: { + threshold: 1, + validators: ['0x773d7fe6ffb1ba4de814c28044ff9a2d83a48221'], + }, + arbitrum: { threshold: 3, validators: [ @@ -41,6 +46,11 @@ export const defaultMultisigConfigs: ChainMap = { ], }, + arbitrumnova: { + threshold: 1, + validators: ['0xd2a5e9123308d187383c87053811a2c21bd8af1f'], + }, + arbitrumsepolia: { threshold: 1, validators: ['0x09fabfbca0b8bf042e2a1161ee5010d147b0f603'], @@ -78,6 +88,11 @@ export const defaultMultisigConfigs: ChainMap = { ], }, + b3: { + threshold: 1, + validators: ['0xd77b516730a836fc41934e7d5864e72c165b934e'], + }, + base: { threshold: 3, validators: [ @@ -284,6 +299,11 @@ export const defaultMultisigConfigs: ChainMap = { ], }, + fantom: { + threshold: 1, + validators: ['0xa779572028e634e16f26af5dfd4fa685f619457d'], + }, + flare: { threshold: 2, validators: [ @@ -344,6 +364,16 @@ export const defaultMultisigConfigs: ChainMap = { ], }, + gravity: { + threshold: 1, + validators: ['0x23d549bf757a02a6f6068e9363196ecd958c974e'], + }, + + harmony: { + threshold: 1, + validators: ['0xd677803a67651974b1c264171b5d7ca8838db8d5'], + }, + holesky: { threshold: 1, validators: ['0x7ab28ad88bb45867137ea823af88e2cb02359c03'], // TODO @@ -381,6 +411,11 @@ export const defaultMultisigConfigs: ChainMap = { ], }, + kaia: { + threshold: 1, + validators: ['0x9de0b3abb221d19719882fa4d61f769fdc2be9a4'], + }, + kroma: { threshold: 2, validators: [ @@ -513,6 +548,11 @@ export const defaultMultisigConfigs: ChainMap = { ], }, + morph: { + threshold: 1, + validators: ['0x4884535f393151ec419add872100d352f71af380'], + }, + neutron: { threshold: 4, validators: [ @@ -556,6 +596,11 @@ export const defaultMultisigConfigs: ChainMap = { validators: ['0x03efe4d0632ee15685d7e8f46dea0a874304aa29'], }, + orderly: { + threshold: 1, + validators: ['0xec3dc91f9fa2ad35edf5842aa764d5573b778bb6'], + }, + osmosis: { threshold: 1, validators: ['0xea483af11c19fa41b16c31d1534c2a486a92bcac'], @@ -700,6 +745,11 @@ export const defaultMultisigConfigs: ChainMap = { ], }, + snaxchain: { + threshold: 1, + validators: ['0x2c25829ae32a772d2a49f6c4b34f8b01fd03ef9e'], + }, + solanadevnet: { threshold: 2, validators: [ @@ -830,6 +880,11 @@ export const defaultMultisigConfigs: ChainMap = { ], }, + zeronetwork: { + threshold: 1, + validators: ['0x1bd9e3f8a90ea1a13b0f2838a1858046368aad87'], + }, + zetachain: { threshold: 3, validators: [ @@ -850,6 +905,11 @@ export const defaultMultisigConfigs: ChainMap = { ], }, + zksync: { + threshold: 1, + validators: ['0xadd1d39ce7a687e32255ac457cf99a6d8c5b5d1a'], + }, + zoramainnet: { threshold: 3, validators: [ diff --git a/typescript/sdk/src/core/HyperlaneCore.ts b/typescript/sdk/src/core/HyperlaneCore.ts index 93f232a44e..8b1425d102 100644 --- a/typescript/sdk/src/core/HyperlaneCore.ts +++ b/typescript/sdk/src/core/HyperlaneCore.ts @@ -42,6 +42,10 @@ import { CoreFactories, coreFactories } from './contracts.js'; import { DispatchEvent } from './events.js'; import { DispatchedMessage } from './types.js'; +// If no metadata is provided, ensure we provide a default of 0x0001. +// We set to 0x0001 instead of 0x0 to ensure it does not break on zksync. +const DEFAULT_METADATA = '0x0001'; + export class HyperlaneCore extends HyperlaneApp { static fromAddressesMap( addressesMap: HyperlaneAddressesMap, @@ -94,7 +98,7 @@ export class HyperlaneCore extends HyperlaneApp { destinationId, recipient, body, - metadata || '0x', + metadata || DEFAULT_METADATA, hook || ethers.constants.AddressZero, ); }; @@ -159,7 +163,7 @@ export class HyperlaneCore extends HyperlaneApp { destinationDomain, recipientBytes32, body, - metadata || '0x', + metadata || DEFAULT_METADATA, hook || ethers.constants.AddressZero, ] as const; From 8ff194dc47ae38d80ea0919a92072d0803cabf24 Mon Sep 17 00:00:00 2001 From: Mantas-M <120508669+Mantas-M@users.noreply.github.com> Date: Tue, 29 Oct 2024 12:34:33 +0000 Subject: [PATCH 41/61] feat: Cosmos RPC client supporting fallback rpcs (#4752) ### Description Added fallback functionality to the Cosmos RPC client ### Drive-by changes N/A ### Related issues - Fixes #3425 ### Backward compatibility Yes ### Testing Unit tests already in place, no additional added, as the generic CosmosFallbackProvider which is used is already tested --- .../src/providers/cosmos/provider.rs | 28 ++++++++-- .../src/providers/grpc/tests.rs | 2 +- .../src/providers/rpc/client.rs | 37 +++++++++---- .../src/providers/rpc/provider.rs | 55 +++++++++++++------ .../hyperlane-cosmos/src/trait_builder.rs | 14 ++--- .../main/chains/hyperlane-cosmos/src/types.rs | 3 +- .../src/settings/parser/connection_parser.rs | 2 +- 7 files changed, 96 insertions(+), 45 deletions(-) diff --git a/rust/main/chains/hyperlane-cosmos/src/providers/cosmos/provider.rs b/rust/main/chains/hyperlane-cosmos/src/providers/cosmos/provider.rs index e718df6102..2ab0388be7 100644 --- a/rust/main/chains/hyperlane-cosmos/src/providers/cosmos/provider.rs +++ b/rust/main/chains/hyperlane-cosmos/src/providers/cosmos/provider.rs @@ -6,6 +6,7 @@ use cosmrs::crypto::PublicKey; use cosmrs::proto::traits::Message; use cosmrs::tx::{MessageExt, SequenceNumber, SignerInfo, SignerPublicKey}; use cosmrs::{proto, AccountId, Any, Coin, Tx}; +use hyperlane_core::rpc_clients::FallbackProvider; use itertools::{any, cloned, Itertools}; use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; @@ -25,6 +26,7 @@ use hyperlane_core::{ use crate::grpc::{WasmGrpcProvider, WasmProvider}; use crate::providers::cosmos::provider::parse::PacketData; use crate::providers::rpc::CosmosRpcClient; +use crate::rpc_clients::CosmosFallbackProvider; use crate::{ ConnectionConf, CosmosAccountId, CosmosAddress, CosmosAmount, HyperlaneCosmosError, Signer, }; @@ -43,7 +45,7 @@ pub struct CosmosProvider { domain: HyperlaneDomain, connection_conf: ConnectionConf, grpc_provider: WasmGrpcProvider, - rpc_client: CosmosRpcClient, + rpc_client: CosmosFallbackProvider, } impl CosmosProvider { @@ -62,13 +64,21 @@ impl CosmosProvider { locator, signer, )?; - let rpc_client = CosmosRpcClient::new(&conf)?; + + let providers = conf + .get_rpc_urls() + .iter() + .map(CosmosRpcClient::new) + .collect::, _>>()?; + let provider = CosmosFallbackProvider::new( + FallbackProvider::builder().add_providers(providers).build(), + ); Ok(Self { domain, connection_conf: conf, grpc_provider, - rpc_client, + rpc_client: provider, }) } @@ -362,7 +372,10 @@ impl HyperlaneChain for CosmosProvider { #[async_trait] impl HyperlaneProvider for CosmosProvider { async fn get_block_by_height(&self, height: u64) -> ChainResult { - let response = self.rpc_client.get_block(height as u32).await?; + let response = self + .rpc_client + .call(|provider| Box::pin(async move { provider.get_block(height as u32).await })) + .await?; let block = response.block; let block_height = block.header.height.value(); @@ -392,7 +405,12 @@ impl HyperlaneProvider for CosmosProvider { let tendermint_hash = Hash::from_bytes(Algorithm::Sha256, hash.as_bytes()) .expect("transaction hash should be of correct size"); - let response = self.rpc_client.get_tx_by_hash(tendermint_hash).await?; + let response = self + .rpc_client + .call(|provider| { + Box::pin(async move { provider.get_tx_by_hash(tendermint_hash).await }) + }) + .await?; let received_hash = H256::from_slice(response.hash.as_bytes()); diff --git a/rust/main/chains/hyperlane-cosmos/src/providers/grpc/tests.rs b/rust/main/chains/hyperlane-cosmos/src/providers/grpc/tests.rs index 2f5ca4abf4..392ab713db 100644 --- a/rust/main/chains/hyperlane-cosmos/src/providers/grpc/tests.rs +++ b/rust/main/chains/hyperlane-cosmos/src/providers/grpc/tests.rs @@ -54,7 +54,7 @@ fn provider(address: &str) -> WasmGrpcProvider { domain.clone(), ConnectionConf::new( vec![Url::parse("http://grpc-kralum.neutron-1.neutron.org:80").unwrap()], - "https://rpc-kralum.neutron-1.neutron.org".to_owned(), + vec![Url::parse("https://rpc-kralum.neutron-1.neutron.org").unwrap()], "neutron-1".to_owned(), "neutron".to_owned(), "untrn".to_owned(), diff --git a/rust/main/chains/hyperlane-cosmos/src/providers/rpc/client.rs b/rust/main/chains/hyperlane-cosmos/src/providers/rpc/client.rs index f3c3930b15..102c04fac3 100644 --- a/rust/main/chains/hyperlane-cosmos/src/providers/rpc/client.rs +++ b/rust/main/chains/hyperlane-cosmos/src/providers/rpc/client.rs @@ -1,10 +1,13 @@ use cosmrs::proto::tendermint::blocksync::BlockResponse; +use hyperlane_core::rpc_clients::BlockNumberGetter; use tendermint::Hash; use tendermint_rpc::client::CompatMode; use tendermint_rpc::endpoint::{block, block_by_hash, block_results, tx}; -use tendermint_rpc::{Client, HttpClient}; +use tendermint_rpc::{Client, HttpClient, HttpClientUrl, Url as TendermintUrl}; -use hyperlane_core::ChainResult; +use hyperlane_core::{ChainCommunicationError, ChainResult}; +use tonic::async_trait; +use url::Url; use crate::{ConnectionConf, HyperlaneCosmosError}; @@ -16,16 +19,17 @@ pub struct CosmosRpcClient { impl CosmosRpcClient { /// Create new `CosmosRpcClient` - pub fn new(conf: &ConnectionConf) -> ChainResult { - let client = HttpClient::builder( - conf.get_rpc_url() - .parse() - .map_err(Into::::into)?, - ) - // Consider supporting different compatibility modes. - .compat_mode(CompatMode::latest()) - .build() - .map_err(Into::::into)?; + pub fn new(url: &Url) -> ChainResult { + let tendermint_url = tendermint_rpc::Url::try_from(url.to_owned()) + .map_err(Into::::into)?; + let url = tendermint_rpc::HttpClientUrl::try_from(tendermint_url) + .map_err(Into::::into)?; + + let client = HttpClient::builder(url) + // Consider supporting different compatibility modes. + .compat_mode(CompatMode::latest()) + .build() + .map_err(Into::::into)?; Ok(Self { client }) } @@ -75,3 +79,12 @@ impl CosmosRpcClient { .map_err(Into::::into)?) } } + +#[async_trait] +impl BlockNumberGetter for CosmosRpcClient { + async fn get_block_number(&self) -> Result { + self.get_latest_block() + .await + .map(|block| block.block.header.height.value()) + } +} diff --git a/rust/main/chains/hyperlane-cosmos/src/providers/rpc/provider.rs b/rust/main/chains/hyperlane-cosmos/src/providers/rpc/provider.rs index 20b6ac7147..599a751167 100644 --- a/rust/main/chains/hyperlane-cosmos/src/providers/rpc/provider.rs +++ b/rust/main/chains/hyperlane-cosmos/src/providers/rpc/provider.rs @@ -4,13 +4,14 @@ use async_trait::async_trait; use cosmrs::cosmwasm::MsgExecuteContract; use cosmrs::rpc::client::Client; use futures::StreamExt; +use hyperlane_core::rpc_clients::{BlockNumberGetter, FallbackProvider}; use sha256::digest; use tendermint::abci::{Event, EventAttribute}; use tendermint::hash::Algorithm; use tendermint::Hash; use tendermint_rpc::client::CompatMode; use tendermint_rpc::endpoint::block::Response as BlockResponse; -use tendermint_rpc::endpoint::block_results::Response as BlockResultsResponse; +use tendermint_rpc::endpoint::block_results::{self, Response as BlockResultsResponse}; use tendermint_rpc::endpoint::tx; use tendermint_rpc::HttpClient; use time::OffsetDateTime; @@ -21,6 +22,7 @@ use hyperlane_core::{ }; use crate::rpc::CosmosRpcClient; +use crate::rpc_clients::CosmosFallbackProvider; use crate::{ConnectionConf, CosmosAddress, CosmosProvider, HyperlaneCosmosError}; #[async_trait] @@ -79,7 +81,7 @@ pub struct CosmosWasmRpcProvider { contract_address: CosmosAddress, target_event_kind: String, reorg_period: u32, - rpc_client: CosmosRpcClient, + rpc_client: CosmosFallbackProvider, } impl CosmosWasmRpcProvider { @@ -92,7 +94,15 @@ impl CosmosWasmRpcProvider { event_type: String, reorg_period: u32, ) -> ChainResult { - let rpc_client = CosmosRpcClient::new(&conf)?; + let providers = conf + .get_rpc_urls() + .iter() + .map(CosmosRpcClient::new) + .collect::, _>>()?; + let mut builder = FallbackProvider::builder(); + builder = builder.add_providers(providers); + let fallback_provider = builder.build(); + let provider = CosmosFallbackProvider::new(fallback_provider); Ok(Self { domain: locator.domain.clone(), @@ -103,9 +113,15 @@ impl CosmosWasmRpcProvider { )?, target_event_kind: format!("{}-{}", Self::WASM_TYPE, event_type), reorg_period, - rpc_client, + rpc_client: provider, }) } + + async fn get_block(&self, height: u32) -> ChainResult { + self.rpc_client + .call(|provider| Box::pin(async move { provider.get_block(height).await })) + .await + } } impl CosmosWasmRpcProvider { @@ -222,7 +238,10 @@ impl WasmRpcProvider for CosmosWasmRpcProvider { #[instrument(err, skip(self))] #[allow(clippy::blocks_in_conditions)] // TODO: `rustc` 1.80.1 clippy issue async fn get_finalized_block_number(&self) -> ChainResult { - let latest_block = self.rpc_client.get_latest_block().await?; + let latest_block = self + .rpc_client + .call(|provider| Box::pin(async move { provider.get_latest_block().await })) + .await?; let latest_height: u32 = latest_block .block .header @@ -244,15 +263,16 @@ impl WasmRpcProvider for CosmosWasmRpcProvider { where T: Send + Sync + PartialEq + Debug + 'static, { - debug!(?block_number, cursor_label, domain=?self.domain, "Getting logs in block"); - // The two calls below could be made in parallel, but on cosmos rate limiting is a bigger problem // than indexing latency, so we do them sequentially. - let block = self.rpc_client.get_block(block_number).await?; - + let block = self.get_block(block_number).await?; debug!(?block_number, block_hash = ?block.block_id.hash, cursor_label, domain=?self.domain, "Getting logs in block with hash"); - - let block_results = self.rpc_client.get_block_results(block_number).await?; + let block_results = self + .rpc_client + .call(|provider| { + Box::pin(async move { provider.get_block_results(block_number).await }) + }) + .await?; Ok(self.handle_txs(block, block_results, parser, cursor_label)) } @@ -268,17 +288,16 @@ impl WasmRpcProvider for CosmosWasmRpcProvider { where T: Send + Sync + PartialEq + Debug + 'static, { - debug!(?hash, cursor_label, domain=?self.domain, "Getting logs in transaction"); - - let tx = self.rpc_client.get_tx_by_hash(hash).await?; - + let tx = self + .rpc_client + .call(|provider| Box::pin(async move { provider.get_tx_by_hash(hash).await })) + .await?; let block_number = tx.height.value() as u32; - let block = self.rpc_client.get_block(block_number).await?; + let block = self.get_block(block_number).await?; + let block_hash = H256::from_slice(block.block_id.hash.as_bytes()); debug!(?block_number, block_hash = ?block.block_id.hash, cursor_label, domain=?self.domain, "Getting logs in transaction: block info"); - let block_hash = H256::from_slice(block.block_id.hash.as_bytes()); - Ok(self.handle_tx(tx, block_hash, parser).collect()) } } diff --git a/rust/main/chains/hyperlane-cosmos/src/trait_builder.rs b/rust/main/chains/hyperlane-cosmos/src/trait_builder.rs index c48c3611d9..c96d73c471 100644 --- a/rust/main/chains/hyperlane-cosmos/src/trait_builder.rs +++ b/rust/main/chains/hyperlane-cosmos/src/trait_builder.rs @@ -8,10 +8,10 @@ use hyperlane_core::{config::OperationBatchConfig, ChainCommunicationError, Fixe /// Cosmos connection configuration #[derive(Debug, Clone)] pub struct ConnectionConf { - /// The GRPC url to connect to + /// The GRPC urls to connect to grpc_urls: Vec, /// The RPC url to connect to - rpc_url: String, + rpc_urls: Vec, /// The chain ID chain_id: String, /// The human readable address prefix for the chains using bech32. @@ -95,9 +95,9 @@ impl ConnectionConf { self.grpc_urls.clone() } - /// Get the RPC url - pub fn get_rpc_url(&self) -> String { - self.rpc_url.clone() + /// Get the RPC urls + pub fn get_rpc_urls(&self) -> Vec { + self.rpc_urls.clone() } /// Get the chain ID @@ -134,7 +134,7 @@ impl ConnectionConf { #[allow(clippy::too_many_arguments)] pub fn new( grpc_urls: Vec, - rpc_url: String, + rpc_urls: Vec, chain_id: String, bech32_prefix: String, canonical_asset: String, @@ -145,7 +145,7 @@ impl ConnectionConf { ) -> Self { Self { grpc_urls, - rpc_url, + rpc_urls, chain_id, bech32_prefix, canonical_asset, diff --git a/rust/main/chains/hyperlane-cosmos/src/types.rs b/rust/main/chains/hyperlane-cosmos/src/types.rs index aa5a954650..bba61fb010 100644 --- a/rust/main/chains/hyperlane-cosmos/src/types.rs +++ b/rust/main/chains/hyperlane-cosmos/src/types.rs @@ -1,5 +1,6 @@ -use cosmrs::proto::cosmos::base::abci::v1beta1::TxResponse; +use cosmrs::proto::{cosmos::base::abci::v1beta1::TxResponse, tendermint::Error}; use hyperlane_core::{ChainResult, ModuleType, TxOutcome, H256, U256}; +use url::Url; pub struct IsmType(pub hyperlane_cosmwasm_interface::ism::IsmType); diff --git a/rust/main/hyperlane-base/src/settings/parser/connection_parser.rs b/rust/main/hyperlane-base/src/settings/parser/connection_parser.rs index 4a435444df..67edcfbc5f 100644 --- a/rust/main/hyperlane-base/src/settings/parser/connection_parser.rs +++ b/rust/main/hyperlane-base/src/settings/parser/connection_parser.rs @@ -162,7 +162,7 @@ pub fn build_cosmos_connection_conf( } else { Some(ChainConnectionConf::Cosmos(h_cosmos::ConnectionConf::new( grpcs, - rpcs.first().unwrap().to_string(), + rpcs.to_owned(), chain_id.unwrap().to_string(), prefix.unwrap().to_string(), canonical_asset.unwrap(), From f438d442a5ad06bd76c0d5d8f23d65c7f1437db4 Mon Sep 17 00:00:00 2001 From: Jason Guo <33064781+Xaroz@users.noreply.github.com> Date: Tue, 29 Oct 2024 11:36:01 -0400 Subject: [PATCH 42/61] feat: widget update for de-duplication (#4771) ### Description Update widgets with new components from the hyperlane explorer and warp UI. These will be used to replace the current components from both mentioned repo ### Drive-by changes None ### Backward compatibility Not exactly, Spinner and WideChevron were renamed to SpinnerIcon and WideChevronIcon, these must be updated accordingly ### Testing Manual and Visual Testing with storybook --- .changeset/plenty-pens-peel.md | 11 ++ typescript/widgets/src/animations/Fade.tsx | 28 +++++ .../widgets/src/chains/ChainDetailsMenu.tsx | 4 +- .../widgets/src/components/DatetimeField.tsx | 36 +++++++ .../widgets/src/components/SelectField.tsx | 45 ++++++++ typescript/widgets/src/icons/Arrow.tsx | 10 +- typescript/widgets/src/icons/Discord.tsx | 25 +++++ typescript/widgets/src/icons/Docs.tsx | 18 ++++ typescript/widgets/src/icons/Github.tsx | 18 ++++ typescript/widgets/src/icons/History.tsx | 18 ++++ typescript/widgets/src/icons/LinkedIn.tsx | 16 +++ typescript/widgets/src/icons/Medium.tsx | 24 +++++ typescript/widgets/src/icons/Spinner.tsx | 2 +- typescript/widgets/src/icons/Twitter.tsx | 18 ++++ typescript/widgets/src/icons/Wallet.tsx | 18 ++++ typescript/widgets/src/icons/Web.tsx | 68 ++++++++++++ typescript/widgets/src/icons/WideChevron.tsx | 2 +- typescript/widgets/src/index.ts | 21 +++- .../widgets/src/messages/MessageTimeline.tsx | 11 +- .../widgets/src/stories/Fade.stories.tsx | 21 ++++ .../widgets/src/stories/IconList.stories.tsx | 102 ++++++++++++++++++ .../src/stories/WideChevron.stories.tsx | 12 +-- 22 files changed, 508 insertions(+), 20 deletions(-) create mode 100644 .changeset/plenty-pens-peel.md create mode 100644 typescript/widgets/src/animations/Fade.tsx create mode 100644 typescript/widgets/src/components/DatetimeField.tsx create mode 100644 typescript/widgets/src/components/SelectField.tsx create mode 100644 typescript/widgets/src/icons/Discord.tsx create mode 100644 typescript/widgets/src/icons/Docs.tsx create mode 100644 typescript/widgets/src/icons/Github.tsx create mode 100644 typescript/widgets/src/icons/History.tsx create mode 100644 typescript/widgets/src/icons/LinkedIn.tsx create mode 100644 typescript/widgets/src/icons/Medium.tsx create mode 100644 typescript/widgets/src/icons/Twitter.tsx create mode 100644 typescript/widgets/src/icons/Wallet.tsx create mode 100644 typescript/widgets/src/icons/Web.tsx create mode 100644 typescript/widgets/src/stories/Fade.stories.tsx create mode 100644 typescript/widgets/src/stories/IconList.stories.tsx diff --git a/.changeset/plenty-pens-peel.md b/.changeset/plenty-pens-peel.md new file mode 100644 index 0000000000..9b69b8003d --- /dev/null +++ b/.changeset/plenty-pens-peel.md @@ -0,0 +1,11 @@ +--- +'@hyperlane-xyz/widgets': minor +--- + +Update widgets with components from explorer and warp ui + +- Add icons: Discord, Docs, Github, History, LinkedIn, Medium, Twitter, Wallet and Web +- Add animation component: Fade component +- Add components: DatetimeField and SelectField +- New stories: IconList and Fade +- Add "Icon" suffix for icons that did not have it diff --git a/typescript/widgets/src/animations/Fade.tsx b/typescript/widgets/src/animations/Fade.tsx new file mode 100644 index 0000000000..5e98e53cfb --- /dev/null +++ b/typescript/widgets/src/animations/Fade.tsx @@ -0,0 +1,28 @@ +import React, { PropsWithChildren, useEffect, useState } from 'react'; + +export function Fade(props: PropsWithChildren<{ show: boolean }>) { + const { show, children } = props; + const [render, setRender] = useState(show); + + useEffect(() => { + if (show) setRender(true); + }, [show]); + + const onAnimationEnd = () => { + if (!show) setRender(false); + }; + + return render ? ( +
+ {children} +
+ ) : null; +} diff --git a/typescript/widgets/src/chains/ChainDetailsMenu.tsx b/typescript/widgets/src/chains/ChainDetailsMenu.tsx index 15478fbbaa..7c175d0d88 100644 --- a/typescript/widgets/src/chains/ChainDetailsMenu.tsx +++ b/typescript/widgets/src/chains/ChainDetailsMenu.tsx @@ -30,7 +30,7 @@ import { CheckmarkIcon } from '../icons/Checkmark.js'; import { ChevronIcon } from '../icons/Chevron.js'; import { Circle } from '../icons/Circle.js'; import { PlusCircleIcon } from '../icons/PlusCircle.js'; -import { Spinner } from '../icons/Spinner.js'; +import { SpinnerIcon } from '../icons/Spinner.js'; import { XIcon } from '../icons/X.js'; import { useConnectionHealthTest } from '../utils/useChainConnectionTest.js'; @@ -450,7 +450,7 @@ function ConnectionRow({ return (
{isNullish(isHealthy) && type == ChainConnectionType.RPC && ( - + )} {isNullish(isHealthy) && type == ChainConnectionType.Explorer && ( diff --git a/typescript/widgets/src/components/DatetimeField.tsx b/typescript/widgets/src/components/DatetimeField.tsx new file mode 100644 index 0000000000..cc04060381 --- /dev/null +++ b/typescript/widgets/src/components/DatetimeField.tsx @@ -0,0 +1,36 @@ +import React, { ChangeEvent } from 'react'; + +interface Props { + className?: string; + timestamp: number | null; + onChange: (t: number | null) => void; + name?: string; +} + +export function DatetimeField({ className, timestamp, onChange, name }: Props) { + const handleChange = (e: ChangeEvent) => { + if (!e.target['validity'].valid) { + onChange(null); + } else { + const datetime = e.target['value'] + ':00Z'; + const newTimestamp = new Date(datetime).getTime(); + onChange(newTimestamp); + } + }; + + return ( + + ); +} + +function toShortIsoString(timestamp: number | null) { + if (!timestamp) return ''; + // Trim milliseconds and timezone to match input field format + return new Date(timestamp).toISOString().split('.')[0]; +} diff --git a/typescript/widgets/src/components/SelectField.tsx b/typescript/widgets/src/components/SelectField.tsx new file mode 100644 index 0000000000..11ed0e1d00 --- /dev/null +++ b/typescript/widgets/src/components/SelectField.tsx @@ -0,0 +1,45 @@ +import React, { ChangeEvent } from 'react'; + +export type SelectOption = { + display: string; + value: string; +}; + +type Props = React.DetailedHTMLProps< + React.SelectHTMLAttributes, + HTMLSelectElement +> & { + options: Array; + value: string; + onValueSelect: (value: string) => void; + classes?: string; +}; + +export function SelectField({ + options, + value, + onValueSelect, + classes, + ...passThruProps +}: Props) { + const onChangeSelect = (event: ChangeEvent) => { + onValueSelect(event?.target?.value || ''); + }; + + return ( + + ); +} diff --git a/typescript/widgets/src/icons/Arrow.tsx b/typescript/widgets/src/icons/Arrow.tsx index 3d066af9ca..d4061a6f63 100644 --- a/typescript/widgets/src/icons/Arrow.tsx +++ b/typescript/widgets/src/icons/Arrow.tsx @@ -1,12 +1,14 @@ -import React, { SVGProps, memo } from 'react'; +import React, { memo } from 'react'; import { ColorPalette } from '../color.js'; -type Props = SVGProps & { +import { DefaultIconProps } from './types.js'; + +type Props = DefaultIconProps & { direction: 'n' | 'e' | 's' | 'w'; }; -function _ArrowIcon({ fill, className, direction, ...rest }: Props) { +function _ArrowIcon({ color, className, direction, ...rest }: Props) { let directionClass; switch (direction) { case 'n': @@ -36,7 +38,7 @@ function _ArrowIcon({ fill, className, direction, ...rest }: Props) { ); diff --git a/typescript/widgets/src/icons/Discord.tsx b/typescript/widgets/src/icons/Discord.tsx new file mode 100644 index 0000000000..28a3d93f1d --- /dev/null +++ b/typescript/widgets/src/icons/Discord.tsx @@ -0,0 +1,25 @@ +import React, { memo } from 'react'; + +import { ColorPalette } from '../color.js'; + +import { DefaultIconProps } from './types.js'; + +function _Discord({ color, ...rest }: DefaultIconProps) { + return ( + + + + + + + + + + + ); +} + +export const DiscordIcon = memo(_Discord); diff --git a/typescript/widgets/src/icons/Docs.tsx b/typescript/widgets/src/icons/Docs.tsx new file mode 100644 index 0000000000..656c79c0ef --- /dev/null +++ b/typescript/widgets/src/icons/Docs.tsx @@ -0,0 +1,18 @@ +import React, { memo } from 'react'; + +import { ColorPalette } from '../color.js'; + +import { DefaultIconProps } from './types.js'; + +function _DocsIcon({ color, ...rest }: DefaultIconProps) { + return ( + + + + ); +} + +export const DocsIcon = memo(_DocsIcon); diff --git a/typescript/widgets/src/icons/Github.tsx b/typescript/widgets/src/icons/Github.tsx new file mode 100644 index 0000000000..5d41670fb4 --- /dev/null +++ b/typescript/widgets/src/icons/Github.tsx @@ -0,0 +1,18 @@ +import React, { memo } from 'react'; + +import { ColorPalette } from '../color.js'; + +import { DefaultIconProps } from './types.js'; + +function _Github({ color, ...rest }: DefaultIconProps) { + return ( + + + + ); +} + +export const GithubIcon = memo(_Github); diff --git a/typescript/widgets/src/icons/History.tsx b/typescript/widgets/src/icons/History.tsx new file mode 100644 index 0000000000..f6e1852960 --- /dev/null +++ b/typescript/widgets/src/icons/History.tsx @@ -0,0 +1,18 @@ +import React, { memo } from 'react'; + +import { ColorPalette } from '../color.js'; + +import { DefaultIconProps } from './types.js'; + +function _HistoryIcon({ color, ...rest }: DefaultIconProps) { + return ( + + + + ); +} + +export const HistoryIcon = memo(_HistoryIcon); diff --git a/typescript/widgets/src/icons/LinkedIn.tsx b/typescript/widgets/src/icons/LinkedIn.tsx new file mode 100644 index 0000000000..88e042c6dd --- /dev/null +++ b/typescript/widgets/src/icons/LinkedIn.tsx @@ -0,0 +1,16 @@ +import React, { memo } from 'react'; + +import { DefaultIconProps } from './types.js'; + +function _Linkedin({ color, ...rest }: DefaultIconProps) { + return ( + + + + ); +} + +export const LinkedInIcon = memo(_Linkedin); diff --git a/typescript/widgets/src/icons/Medium.tsx b/typescript/widgets/src/icons/Medium.tsx new file mode 100644 index 0000000000..cba9c92d3b --- /dev/null +++ b/typescript/widgets/src/icons/Medium.tsx @@ -0,0 +1,24 @@ +import React, { memo } from 'react'; + +import { ColorPalette } from '../color.js'; + +import { DefaultIconProps } from './types.js'; + +function _Medium({ color, ...rest }: DefaultIconProps) { + return ( + + + + ); +} + +export const MediumIcon = memo(_Medium); diff --git a/typescript/widgets/src/icons/Spinner.tsx b/typescript/widgets/src/icons/Spinner.tsx index 8cb1e8c45c..71501341f0 100644 --- a/typescript/widgets/src/icons/Spinner.tsx +++ b/typescript/widgets/src/icons/Spinner.tsx @@ -30,4 +30,4 @@ function _Spinner({ color, className, ...rest }: DefaultIconProps) { ); } -export const Spinner = memo(_Spinner); +export const SpinnerIcon = memo(_Spinner); diff --git a/typescript/widgets/src/icons/Twitter.tsx b/typescript/widgets/src/icons/Twitter.tsx new file mode 100644 index 0000000000..feffaa4951 --- /dev/null +++ b/typescript/widgets/src/icons/Twitter.tsx @@ -0,0 +1,18 @@ +import React, { memo } from 'react'; + +import { ColorPalette } from '../color.js'; + +import { DefaultIconProps } from './types.js'; + +function _Twitter({ color, ...rest }: DefaultIconProps) { + return ( + + + + ); +} + +export const TwitterIcon = memo(_Twitter); diff --git a/typescript/widgets/src/icons/Wallet.tsx b/typescript/widgets/src/icons/Wallet.tsx new file mode 100644 index 0000000000..7216791ccd --- /dev/null +++ b/typescript/widgets/src/icons/Wallet.tsx @@ -0,0 +1,18 @@ +import React, { memo } from 'react'; + +import { ColorPalette } from '../color.js'; + +import { DefaultIconProps } from './types.js'; + +function _WalletIcon({ color, ...rest }: DefaultIconProps) { + return ( + + + + ); +} + +export const WalletIcon = memo(_WalletIcon); diff --git a/typescript/widgets/src/icons/Web.tsx b/typescript/widgets/src/icons/Web.tsx new file mode 100644 index 0000000000..8915ed026d --- /dev/null +++ b/typescript/widgets/src/icons/Web.tsx @@ -0,0 +1,68 @@ +import React, { memo } from 'react'; + +import { ColorPalette } from '../color.js'; + +import { DefaultIconProps } from './types.js'; + +function _Web({ color = ColorPalette.Black, ...rest }: DefaultIconProps) { + return ( + + + + + + + + + + + + + + + + ); +} + +export const WebIcon = memo(_Web); diff --git a/typescript/widgets/src/icons/WideChevron.tsx b/typescript/widgets/src/icons/WideChevron.tsx index 83ecb8323a..21d15238b1 100644 --- a/typescript/widgets/src/icons/WideChevron.tsx +++ b/typescript/widgets/src/icons/WideChevron.tsx @@ -69,4 +69,4 @@ function _WideChevron({ } } -export const WideChevron = memo(_WideChevron); +export const WideChevronIcon = memo(_WideChevron); diff --git a/typescript/widgets/src/index.ts b/typescript/widgets/src/index.ts index 0a25bc9a50..2bb43315c8 100644 --- a/typescript/widgets/src/index.ts +++ b/typescript/widgets/src/index.ts @@ -1,3 +1,4 @@ +export { Fade } from './animations/Fade.js'; export { ChainDetailsMenu, type ChainDetailsMenuProps, @@ -8,13 +9,16 @@ export { type ChainSearchMenuProps, } from './chains/ChainSearchMenu.js'; export { ColorPalette, seedToBgColor } from './color.js'; +export { Button } from './components/Button.js'; export { CopyButton } from './components/CopyButton.js'; +export { DatetimeField } from './components/DatetimeField.js'; export { IconButton } from './components/IconButton.js'; export { LinkButton } from './components/LinkButton.js'; export { SegmentedControl } from './components/SegmentedControl.js'; +export { SelectField, type SelectOption } from './components/SelectField.js'; export { TextInput } from './components/TextInput.js'; export { Tooltip } from './components/Tooltip.js'; -export * from './consts.js'; +export { HYPERLANE_EXPLORER_API_URL } from './consts.js'; export { AirplaneIcon } from './icons/Airplane.js'; export { ArrowIcon } from './icons/Arrow.js'; export { BoxArrowIcon } from './icons/BoxArrow.js'; @@ -22,20 +26,31 @@ export { CheckmarkIcon } from './icons/Checkmark.js'; export { ChevronIcon } from './icons/Chevron.js'; export { Circle } from './icons/Circle.js'; export { CopyIcon } from './icons/Copy.js'; +export { DiscordIcon } from './icons/Discord.js'; +export { DocsIcon } from './icons/Docs.js'; export { EnvelopeIcon } from './icons/Envelope.js'; export { FilterIcon } from './icons/Filter.js'; export { FunnelIcon } from './icons/Funnel.js'; export { GearIcon } from './icons/Gear.js'; +export { GithubIcon } from './icons/Github.js'; +export { HistoryIcon } from './icons/History.js'; +export { LinkedInIcon } from './icons/LinkedIn.js'; export { LockIcon } from './icons/Lock.js'; +export { MediumIcon } from './icons/Medium.js'; +export { PencilIcon } from './icons/Pencil.js'; export { PlusIcon } from './icons/Plus.js'; export { PlusCircleIcon } from './icons/PlusCircle.js'; export { QuestionMarkIcon } from './icons/QuestionMark.js'; export { SearchIcon } from './icons/Search.js'; export { ShieldIcon } from './icons/Shield.js'; -export { Spinner } from './icons/Spinner.js'; +export { SpinnerIcon } from './icons/Spinner.js'; +export { TwitterIcon } from './icons/Twitter.js'; export { UpDownArrowsIcon } from './icons/UpDownArrows.js'; -export { WideChevron } from './icons/WideChevron.js'; +export { WalletIcon } from './icons/Wallet.js'; +export { WebIcon } from './icons/Web.js'; +export { WideChevronIcon } from './icons/WideChevron.js'; export { XIcon } from './icons/X.js'; +export { type DefaultIconProps } from './icons/types.js'; export { DropdownMenu, type DropdownMenuProps } from './layout/DropdownMenu.js'; export { Modal, useModal, type ModalProps } from './layout/Modal.js'; export { Popover, type PopoverProps } from './layout/Popover.js'; diff --git a/typescript/widgets/src/messages/MessageTimeline.tsx b/typescript/widgets/src/messages/MessageTimeline.tsx index 3bf153ca5a..74a50b976c 100644 --- a/typescript/widgets/src/messages/MessageTimeline.tsx +++ b/typescript/widgets/src/messages/MessageTimeline.tsx @@ -5,7 +5,7 @@ import { AirplaneIcon } from '../icons/Airplane.js'; import { EnvelopeIcon } from '../icons/Envelope.js'; import { LockIcon } from '../icons/Lock.js'; import { ShieldIcon } from '../icons/Shield.js'; -import { WideChevron } from '../icons/WideChevron.js'; +import { WideChevronIcon } from '../icons/WideChevron.js'; import { MessageStatus, MessageStage as Stage, StageTimings } from './types.js'; @@ -157,7 +157,12 @@ function StageIcon({ Icon, size }: { Icon: any; size?: number }) { function ChevronWhite() { return (
- +
); } @@ -165,7 +170,7 @@ function ChevronWhite() { function ChevronBlue() { return (
- +
); } diff --git a/typescript/widgets/src/stories/Fade.stories.tsx b/typescript/widgets/src/stories/Fade.stories.tsx new file mode 100644 index 0000000000..7c6a582b53 --- /dev/null +++ b/typescript/widgets/src/stories/Fade.stories.tsx @@ -0,0 +1,21 @@ +import { Meta, StoryObj } from '@storybook/react'; +import React from 'react'; + +import { Fade } from '../animations/Fade'; + +function MyFadeAnimation({ show }: { show: boolean }) { + return ( + +
Hello Fade
+
+ ); +} + +const meta = { + title: 'Fade', + component: MyFadeAnimation, +} satisfies Meta; +export default meta; +type Story = StoryObj; + +export const BaseFadeAnimation = { args: { show: false } } satisfies Story; diff --git a/typescript/widgets/src/stories/IconList.stories.tsx b/typescript/widgets/src/stories/IconList.stories.tsx new file mode 100644 index 0000000000..48570f106c --- /dev/null +++ b/typescript/widgets/src/stories/IconList.stories.tsx @@ -0,0 +1,102 @@ +import { Meta, StoryObj } from '@storybook/react'; +import React from 'react'; + +import * as Hyperlane from '../index'; + +interface StoryIconProps { + width?: number; + height?: number; + color?: string; + direction?: 'n' | 'e' | 's' | 'w'; + rounded?: boolean; +} + +const iconList = Object.entries(Hyperlane) + .filter(([name]) => name.includes('Icon') && !name.includes('IconButton')) + .map(([_, Component]) => Component as React.ComponentType); + +function IconList({ + width, + height, + color, + direction, + bgColorSeed, + roundedWideChevron, +}: { + width: number; + height: number; + color: string; + direction: 'n' | 'e' | 's' | 'w'; + bgColorSeed: number | undefined; + roundedWideChevron: boolean; +}) { + return ( + <> +
+ {iconList.map((Icon) => ( + + {Icon.displayName} + + + ))} + + Circle + + +
+ + ); +} + +function IconContainer({ children }: { children: React.ReactNode }) { + return ( +
+ {children} +
+ ); +} + +const meta = { + title: 'Icon List', + component: IconList, + argTypes: { + direction: { + options: ['n', 'e', 's', 'w'], + control: { type: 'select' }, + }, + }, +} satisfies Meta; +export default meta; +type Story = StoryObj; + +export const DefaultIconList = { + args: { + width: 24, + height: 24, + color: Hyperlane.ColorPalette.Black, + direction: 's', + bgColorSeed: 0, + roundedWideChevron: false, + }, +} satisfies Story; diff --git a/typescript/widgets/src/stories/WideChevron.stories.tsx b/typescript/widgets/src/stories/WideChevron.stories.tsx index cb81ae5c39..0738b54518 100644 --- a/typescript/widgets/src/stories/WideChevron.stories.tsx +++ b/typescript/widgets/src/stories/WideChevron.stories.tsx @@ -1,16 +1,16 @@ -import { ComponentMeta, ComponentStory } from '@storybook/react'; +import { Meta, StoryFn } from '@storybook/react'; import React from 'react'; import { ColorPalette } from '../color.js'; -import { WideChevron } from '../icons/WideChevron.js'; +import { WideChevronIcon } from '../icons/WideChevron.js'; export default { title: 'WideChevron', - component: WideChevron, -} as ComponentMeta; + component: WideChevronIcon, +} as Meta; -const Template: ComponentStory = (args) => ( - +const Template: StoryFn = (args) => ( + ); export const BlueEastRounded = Template.bind({}); From 83a1567a0ad6a5cc227af650ff9bc5c7d4c92cd1 Mon Sep 17 00:00:00 2001 From: Tien Dao <15717476+tiendn@users.noreply.github.com> Date: Tue, 29 Oct 2024 23:47:28 +0700 Subject: [PATCH 43/61] test: append-more-multisigmIsm-test (#4769) ### Description Append more test for `multisigIsm.ts` ### Drive-by changes ### Related issues ### Backward compatibility ### Testing Add 2 test cases: - has a valid number of validators for each threshold - has valid EVM addresses for each validator --- typescript/sdk/src/consts/multisigIsm.test.ts | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/typescript/sdk/src/consts/multisigIsm.test.ts b/typescript/sdk/src/consts/multisigIsm.test.ts index 58b84c7cb2..53f93dc161 100644 --- a/typescript/sdk/src/consts/multisigIsm.test.ts +++ b/typescript/sdk/src/consts/multisigIsm.test.ts @@ -1,5 +1,7 @@ import { expect } from 'chai'; +import { isAddress } from '@hyperlane-xyz/utils'; + import { defaultMultisigConfigs } from './multisigIsm.js'; describe('MultisigIsm', () => { @@ -13,5 +15,25 @@ describe('MultisigIsm', () => { ); } }); + + it('has a valid number of validators for each threshold', async () => { + for (const [chain, config] of Object.entries(defaultMultisigConfigs)) { + expect(config.validators.length).to.be.greaterThanOrEqual( + config.threshold, + `Number of validators for ${chain} is less than the threshold, expected at least ${config.threshold}, got ${config.validators.length}`, + ); + } + }); + + it('has valid EVM addresses for each validator', async () => { + for (const [chain, config] of Object.entries(defaultMultisigConfigs)) { + for (const validator of config.validators) { + expect(isAddress(validator)).to.equal( + true, + `Validator address ${validator} for ${chain} is not a valid EVM address`, + ); + } + } + }); }); }); From fc3818d12af3c6672e4df9ccc9217c3184897a75 Mon Sep 17 00:00:00 2001 From: Trevor Porter Date: Wed, 30 Oct 2024 11:14:07 +0000 Subject: [PATCH 44/61] feat: add taint & toleration to mainnet3 relayer node pool (#4780) ### Description See https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/ - In GKE, for the larger relayer node pool, added a taint: `NoSchedule`, label `component=relayer` - In our relayer workloads, they now have a toleration that matches that taint - As part of rollout, first did a NoExecute (evicting everything on the big nodes), then moved to NoSchedule (so that some other pods like daemonsets would still get onto these bigger nodes). I made sure the hyperlane and neutron context relayers have these tolerations ### Drive-by changes ### Related issues - Fixes https://github.com/hyperlane-xyz/issues/issues/1309 ### Backward compatibility ### Testing --- typescript/infra/src/agents/index.ts | 13 +++++++++++++ typescript/infra/src/config/agent/agent.ts | 8 ++++++++ 2 files changed, 21 insertions(+) diff --git a/typescript/infra/src/agents/index.ts b/typescript/infra/src/agents/index.ts index ee0da5889a..3f3f6469aa 100644 --- a/typescript/infra/src/agents/index.ts +++ b/typescript/infra/src/agents/index.ts @@ -164,6 +164,19 @@ export class RelayerHelmManager extends OmniscientAgentHelmManager { signer: signers[name], })); + if (!values.tolerations) { + values.tolerations = []; + } + + // Relayer pods should only be scheduled on nodes with the component label set to relayer. + // NoSchedule was chosen so that some daemonsets (like the prometheus node exporter) would not be evicted. + values.tolerations.push({ + key: 'component', + operator: 'Equal', + value: 'relayer', + effect: 'NoSchedule', + }); + return values; } } diff --git a/typescript/infra/src/config/agent/agent.ts b/typescript/infra/src/config/agent/agent.ts index e9ff6f3e40..987a05f0ea 100644 --- a/typescript/infra/src/config/agent/agent.ts +++ b/typescript/infra/src/config/agent/agent.ts @@ -36,6 +36,7 @@ export interface HelmRootAgentValues { image: HelmImageValues; hyperlane: HelmHyperlaneValues; nameOverride?: string; + tolerations?: KubernetesToleration[]; } // See rust/main/helm/values.yaml for the full list of options and their defaults. @@ -132,6 +133,13 @@ export interface KubernetesComputeResources { memory: string; } +export interface KubernetesToleration { + key: string; + operator: string; + value: string; + effect: string; +} + export class RootAgentConfigHelper implements AgentContextConfig { readonly rawConfig: RootAgentConfig; From cca032fb1879f1103fdd229933d8812e7a66a169 Mon Sep 17 00:00:00 2001 From: Kunal Arora <55632507+aroralanuk@users.noreply.github.com> Date: Wed, 30 Oct 2024 17:34:56 +0530 Subject: [PATCH 45/61] fix(contracts): assume no zero address for beneficiary address in `ProtocolFee` fuzz test (#4781) ### Description ### Drive-by changes ### Related issues ### Backward compatibility ### Testing --- solidity/test/hooks/ProtocolFee.t.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/solidity/test/hooks/ProtocolFee.t.sol b/solidity/test/hooks/ProtocolFee.t.sol index bfd90f6872..161d9e9c8c 100644 --- a/solidity/test/hooks/ProtocolFee.t.sol +++ b/solidity/test/hooks/ProtocolFee.t.sol @@ -73,6 +73,7 @@ contract ProtocolFeeTest is Test { } function testSetBeneficiary(address beneficiary) public { + vm.assume(beneficiary != address(0)); vm.expectEmit(true, true, true, true); emit ProtocolFee.BeneficiarySet(beneficiary); fees.setBeneficiary(beneficiary); From fc82b30a2e4337a85ba712c5ab8075a8da8a4d35 Mon Sep 17 00:00:00 2001 From: Mantas-M <120508669+Mantas-M@users.noreply.github.com> Date: Wed, 30 Oct 2024 12:17:13 +0000 Subject: [PATCH 46/61] feat: Refactor Relayer Retry Server (#4693) ### Description Refactor of the Relayer Retry Server to utilize matching lists. This allows for a single POST request to be able to retry a wide range of messages. Since a matching list is used it is now possible to retry messages depending on: - Message Id - Origin Domain - Destination Domain - Recipient Address - Sender Address ## Example Instead of the old request format ``` GET http://127.0.0.1:60843/message_retry?destination_domain=42 GET http://127.0.0.1:60843/message_retry?message_id=0x46910b1329ee53c86a023b322e9ca1c17e5f9f0bee789c77b0abced0a173d714 ``` The new one should be used ``` POST http://127.0.0.1:60843/message_retry ``` With a JSON body like so: ``` [{"messageid": "*", "origindomain": "*", "senderaddress": "*", "destinationdomain": "*", "recipientaddress": "*"}] ``` Retry params can be both specific values and wildcards. ### Drive-by changes - Moved `MockPendingOperation` to `hyperlane-test` - Moved certain test utils for the relayer server to `hyperlane-test` - Added functions to retrieve recipient and sender address from the `PendingOperation` - Added a `message_id` to the `MatchingList` struct to allow retries by message id - Removed `MessageRetryRequest` enum since it was no longer in use ### Related issues Fixes #3571 #4079 ### Backward compatibility Yes ### Testing Unit tests --- rust/main/agents/relayer/src/msg/op_queue.rs | 87 ++++-- .../agents/relayer/src/msg/op_submitter.rs | 4 +- .../agents/relayer/src/msg/pending_message.rs | 8 + rust/main/agents/relayer/src/relayer.rs | 4 +- .../relayer/src/server/list_messages.rs | 28 +- .../relayer/src/server/message_retry.rs | 280 +++++++++++------- rust/main/agents/relayer/src/server/mod.rs | 6 +- .../relayer/src/settings/matching_list.rs | 65 +++- .../src/traits/pending_operation.rs | 6 + 9 files changed, 352 insertions(+), 136 deletions(-) diff --git a/rust/main/agents/relayer/src/msg/op_queue.rs b/rust/main/agents/relayer/src/msg/op_queue.rs index d22e0c3c83..95025d391c 100644 --- a/rust/main/agents/relayer/src/msg/op_queue.rs +++ b/rust/main/agents/relayer/src/msg/op_queue.rs @@ -6,7 +6,7 @@ use prometheus::{IntGauge, IntGaugeVec}; use tokio::sync::{broadcast::Receiver, Mutex}; use tracing::{debug, info, instrument}; -use crate::server::MessageRetryRequest; +use crate::settings::matching_list::MatchingList; pub type OperationPriorityQueue = Arc>>>; @@ -16,7 +16,7 @@ pub type OperationPriorityQueue = Arc>> pub struct OpQueue { metrics: IntGaugeVec, queue_metrics_label: String, - retry_rx: Arc>>, + retry_rx: Arc>>, #[new(default)] pub queue: OperationPriorityQueue, } @@ -84,9 +84,7 @@ impl OpQueue { let mut reprioritized_queue: BinaryHeap<_> = queue .drain() .map(|Reverse(mut op)| { - // Can check for equality here because of the PartialEq implementation for MessageRetryRequest, - // but can't use `contains` because the types are different - if message_retry_requests.iter().any(|r| r == op) { + if message_retry_requests.iter().any(|r| r.op_matches(&op)) { info!( operation = %op, queue_label = %self.queue_metrics_label, @@ -116,12 +114,14 @@ impl OpQueue { pub mod test { use super::*; use hyperlane_core::{ - HyperlaneDomain, HyperlaneMessage, KnownHyperlaneDomain, PendingOperationResult, + HyperlaneDomain, HyperlaneDomainProtocol, HyperlaneDomainTechnicalStack, + HyperlaneDomainType, HyperlaneMessage, KnownHyperlaneDomain, PendingOperationResult, TryBatchAs, TxOutcome, H256, U256, }; use serde::Serialize; use std::{ collections::VecDeque, + str::FromStr, time::{Duration, Instant}, }; use tokio::sync; @@ -129,6 +129,10 @@ pub mod test { #[derive(Debug, Clone, Serialize)] pub struct MockPendingOperation { id: H256, + sender_address: H256, + origin_domain_id: u32, + destination_domain_id: u32, + recipient_address: H256, seconds_to_next_attempt: u64, destination_domain: HyperlaneDomain, } @@ -138,12 +142,51 @@ pub mod test { Self { id: H256::random(), seconds_to_next_attempt, + destination_domain_id: destination_domain.id(), destination_domain, + sender_address: H256::random(), + recipient_address: H256::random(), + origin_domain_id: 0, } } - pub fn with_id(self, id: H256) -> Self { - Self { id, ..self } + pub fn with_message_data(message: HyperlaneMessage) -> Self { + Self { + id: message.id(), + sender_address: message.sender, + recipient_address: message.recipient, + origin_domain_id: message.origin, + destination_domain_id: message.destination, + seconds_to_next_attempt: 0, + destination_domain: HyperlaneDomain::Unknown { + domain_id: message.destination, + domain_name: "test".to_string(), + domain_type: HyperlaneDomainType::Unknown, + domain_protocol: HyperlaneDomainProtocol::Ethereum, + domain_technical_stack: HyperlaneDomainTechnicalStack::Other, + }, + } + } + + pub fn with_id(self, id: &str) -> Self { + Self { + id: H256::from_str(id).unwrap(), + ..self + } + } + + pub fn with_sender_address(self, sender_address: &str) -> Self { + Self { + sender_address: H256::from_str(sender_address).unwrap(), + ..self + } + } + + pub fn with_recipient_address(self, recipient_address: &str) -> Self { + Self { + recipient_address: H256::from_str(recipient_address).unwrap(), + ..self + } } } @@ -166,6 +209,20 @@ pub mod test { self.seconds_to_next_attempt = 0; } + fn sender_address(&self) -> &H256 { + &self.sender_address + } + + fn recipient_address(&self) -> &H256 { + &self.recipient_address + } + + fn get_metric(&self) -> Option> { + None + } + + fn set_metric(&mut self, _metric: Arc) {} + fn priority(&self) -> u32 { todo!() } @@ -179,7 +236,7 @@ pub mod test { } fn origin_domain_id(&self) -> u32 { - todo!() + self.origin_domain_id } fn destination_domain(&self) -> &HyperlaneDomain { @@ -238,12 +295,6 @@ pub mod test { fn set_retries(&mut self, _retries: u32) { todo!() } - - fn get_metric(&self) -> Option> { - None - } - - fn set_metric(&mut self, _metric: Arc) {} } pub fn dummy_metrics_and_label() -> (IntGaugeVec, String) { @@ -312,10 +363,10 @@ pub mod test { // Retry by message ids broadcaster - .send(MessageRetryRequest::MessageId(op_ids[1])) + .send(MatchingList::with_message_id(op_ids[1])) .unwrap(); broadcaster - .send(MessageRetryRequest::MessageId(op_ids[2])) + .send(MatchingList::with_message_id(op_ids[2])) .unwrap(); // Pop elements from queue 1 @@ -373,7 +424,7 @@ pub mod test { // Retry by domain broadcaster - .send(MessageRetryRequest::DestinationDomain( + .send(MatchingList::with_destination_domain( destination_domain_2.id(), )) .unwrap(); diff --git a/rust/main/agents/relayer/src/msg/op_submitter.rs b/rust/main/agents/relayer/src/msg/op_submitter.rs index f35a991c45..c1e295a24a 100644 --- a/rust/main/agents/relayer/src/msg/op_submitter.rs +++ b/rust/main/agents/relayer/src/msg/op_submitter.rs @@ -32,7 +32,7 @@ use hyperlane_core::{ }; use crate::msg::pending_message::CONFIRM_DELAY; -use crate::server::MessageRetryRequest; +use crate::settings::matching_list::MatchingList; use super::op_queue::OpQueue; use super::op_queue::OperationPriorityQueue; @@ -105,7 +105,7 @@ impl SerialSubmitter { pub fn new( domain: HyperlaneDomain, rx: mpsc::UnboundedReceiver, - retry_op_transmitter: Sender, + retry_op_transmitter: Sender, metrics: SerialSubmitterMetrics, max_batch_size: u32, task_monitor: TaskMonitor, diff --git a/rust/main/agents/relayer/src/msg/pending_message.rs b/rust/main/agents/relayer/src/msg/pending_message.rs index 9d43595b5e..cee00aed78 100644 --- a/rust/main/agents/relayer/src/msg/pending_message.rs +++ b/rust/main/agents/relayer/src/msg/pending_message.rs @@ -167,6 +167,14 @@ impl PendingOperation for PendingMessage { self.ctx.destination_mailbox.domain() } + fn sender_address(&self) -> &H256 { + &self.message.sender + } + + fn recipient_address(&self) -> &H256 { + &self.message.recipient + } + fn retrieve_status_from_db(&self) -> Option { match self.ctx.origin_db.retrieve_status_by_message_id(&self.id()) { Ok(status) => status, diff --git a/rust/main/agents/relayer/src/relayer.rs b/rust/main/agents/relayer/src/relayer.rs index d5077d678f..975f372426 100644 --- a/rust/main/agents/relayer/src/relayer.rs +++ b/rust/main/agents/relayer/src/relayer.rs @@ -41,7 +41,7 @@ use crate::{ pending_message::{MessageContext, MessageSubmissionMetrics}, processor::{MessageProcessor, MessageProcessorMetrics}, }, - server::{self as relayer_server, MessageRetryRequest}, + server::{self as relayer_server}, settings::{matching_list::MatchingList, RelayerSettings}, }; use crate::{ @@ -311,7 +311,7 @@ impl BaseAgent for Relayer { })); tasks.push(console_server.instrument(info_span!("Tokio console server"))); } - let sender = BroadcastSender::::new(ENDPOINT_MESSAGES_QUEUE_SIZE); + let sender = BroadcastSender::::new(ENDPOINT_MESSAGES_QUEUE_SIZE); // send channels by destination chain let mut send_channels = HashMap::with_capacity(self.destination_chains.len()); let mut prep_queues = HashMap::with_capacity(self.destination_chains.len()); diff --git a/rust/main/agents/relayer/src/server/list_messages.rs b/rust/main/agents/relayer/src/server/list_messages.rs index 8eea8446b3..e21f39a5df 100644 --- a/rust/main/agents/relayer/src/server/list_messages.rs +++ b/rust/main/agents/relayer/src/server/list_messages.rs @@ -79,8 +79,6 @@ impl ListOperationsApi { } } -// TODO: there's some duplication between the setup for these tests and the one in `message_retry.rs`, -// which should be refactored into a common test setup. #[cfg(test)] mod tests { use crate::msg::op_queue::{ @@ -91,7 +89,7 @@ mod tests { use super::*; use axum::http::StatusCode; use hyperlane_core::KnownHyperlaneDomain; - use std::{cmp::Reverse, net::SocketAddr, str::FromStr, sync::Arc}; + use std::{cmp::Reverse, net::SocketAddr, sync::Arc}; use tokio::sync::{self, Mutex}; const DUMMY_DOMAIN: KnownHyperlaneDomain = KnownHyperlaneDomain::Arbitrum; @@ -109,6 +107,7 @@ mod tests { let list_operations_api = ListOperationsApi::new(op_queues_map); let (path, router) = list_operations_api.get_route(); + let app = Router::new().nest(path, router); // Running the app in the background using a test server @@ -125,13 +124,20 @@ mod tests { let (addr, op_queue) = setup_test_server(); let id_1 = "0x1acbee9798118b11ebef0d94b0a2936eafd58e3bfab91b05da875825c4a1c39b"; let id_2 = "0x51e7be221ce90a49dee46ca0d0270c48d338a7b9d85c2a89d83fac0816571914"; + let sender_address = "0x586d41b02fb35df0f84ecb2b73e076b40c929ee3e1ceeada9a078aa7b46d3b08"; + let recipient_address = + "0x586d41b02fb35df0f84ecb2b73e076b40c929ee3e1ceeada9a078aa7b46d3b08"; let dummy_operation_1 = Box::new( MockPendingOperation::new(1, DUMMY_DOMAIN.into()) - .with_id(H256::from_str(id_1).unwrap()), + .with_id(id_1) + .with_sender_address(sender_address) + .with_recipient_address(recipient_address), ) as QueueOperation; let dummy_operation_2 = Box::new( MockPendingOperation::new(2, DUMMY_DOMAIN.into()) - .with_id(H256::from_str(id_2).unwrap()), + .with_id(id_2) + .with_sender_address(sender_address) + .with_recipient_address(recipient_address), ) as QueueOperation; // The reason there already is an id inside `operation` here is because it's a field on `MockPendingOperation` - that field is @@ -143,8 +149,12 @@ mod tests { "destination_domain": { "Known": "Arbitrum" }, + "destination_domain_id": 42161, "id": "0x1acbee9798118b11ebef0d94b0a2936eafd58e3bfab91b05da875825c4a1c39b", + "origin_domain_id": 0, + "recipient_address": "0x586d41b02fb35df0f84ecb2b73e076b40c929ee3e1ceeada9a078aa7b46d3b08", "seconds_to_next_attempt": 1, + "sender_address": "0x586d41b02fb35df0f84ecb2b73e076b40c929ee3e1ceeada9a078aa7b46d3b08", "type": "MockPendingOperation" } }, @@ -154,8 +164,12 @@ mod tests { "destination_domain": { "Known": "Arbitrum" }, + "destination_domain_id": 42161, "id": "0x51e7be221ce90a49dee46ca0d0270c48d338a7b9d85c2a89d83fac0816571914", + "origin_domain_id": 0, + "recipient_address": "0x586d41b02fb35df0f84ecb2b73e076b40c929ee3e1ceeada9a078aa7b46d3b08", "seconds_to_next_attempt": 2, + "sender_address": "0x586d41b02fb35df0f84ecb2b73e076b40c929ee3e1ceeada9a078aa7b46d3b08", "type": "MockPendingOperation" } } @@ -173,6 +187,8 @@ mod tests { // Check that the response status code is OK assert_eq!(response.status(), StatusCode::OK); - assert_eq!(response.text().await.unwrap(), expected_response); + + let response_text = response.text().await.unwrap(); + assert_eq!(response_text, expected_response); } } diff --git a/rust/main/agents/relayer/src/server/message_retry.rs b/rust/main/agents/relayer/src/server/message_retry.rs index 3fff125eea..6d04eed86a 100644 --- a/rust/main/agents/relayer/src/server/message_retry.rs +++ b/rust/main/agents/relayer/src/server/message_retry.rs @@ -1,90 +1,31 @@ -use axum::{ - extract::{Query, State}, - routing, Router, -}; +use crate::settings::matching_list::MatchingList; +use axum::{extract::State, routing, Json, Router}; use derive_new::new; -use hyperlane_core::{ChainCommunicationError, QueueOperation, H256}; -use serde::Deserialize; -use std::str::FromStr; use tokio::sync::broadcast::Sender; const MESSAGE_RETRY_API_BASE: &str = "/message_retry"; -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum MessageRetryRequest { - MessageId(H256), - DestinationDomain(u32), -} - -impl PartialEq for &MessageRetryRequest { - fn eq(&self, other: &QueueOperation) -> bool { - match self { - MessageRetryRequest::MessageId(message_id) => message_id == &other.id(), - MessageRetryRequest::DestinationDomain(destination_domain) => { - destination_domain == &other.destination_domain().id() - } - } - } -} - #[derive(new, Clone)] pub struct MessageRetryApi { - tx: Sender, -} - -#[derive(Deserialize)] -struct RawMessageRetryRequest { - message_id: Option, - destination_domain: Option, -} - -impl TryFrom for Vec { - type Error = ChainCommunicationError; - - fn try_from(request: RawMessageRetryRequest) -> Result { - let mut retry_requests = Vec::new(); - if let Some(message_id) = request.message_id { - retry_requests.push(MessageRetryRequest::MessageId(H256::from_str(&message_id)?)); - } - if let Some(destination_domain) = request.destination_domain { - retry_requests.push(MessageRetryRequest::DestinationDomain(destination_domain)); - } - Ok(retry_requests) - } + tx: Sender, } async fn retry_message( - State(tx): State>, - Query(request): Query, + State(tx): State>, + Json(retry_req_payload): Json, ) -> String { - let retry_requests: Vec = match request.try_into() { - Ok(retry_requests) => retry_requests, + match tx.send(retry_req_payload) { + Ok(_) => "Moved message(s) to the front of the queue".to_string(), // Technically it's bad practice to print the error message to the user, but // this endpoint is for debugging purposes only. - Err(err) => { - return format!("Failed to parse retry request: {}", err); - } - }; - - if retry_requests.is_empty() { - return "No retry requests found. Please provide either a message_id or destination_domain.".to_string(); + Err(err) => format!("Failed to send retry request to the queue: {}", err), } - - if let Err(err) = retry_requests - .into_iter() - .map(|req| tx.send(req)) - .collect::, _>>() - { - return format!("Failed to send retry request to the queue: {}", err); - } - - "Moved message(s) to the front of the queue".to_string() } impl MessageRetryApi { pub fn router(&self) -> Router { Router::new() - .route("/", routing::get(retry_message)) + .route("/", routing::post(retry_message)) .with_state(self.tx.clone()) } @@ -95,18 +36,20 @@ impl MessageRetryApi { #[cfg(test)] mod tests { - use crate::server::ENDPOINT_MESSAGES_QUEUE_SIZE; + use crate::{msg::op_queue::test::MockPendingOperation, server::ENDPOINT_MESSAGES_QUEUE_SIZE}; use super::*; use axum::http::StatusCode; - use ethers::utils::hex::ToHex; + use hyperlane_core::{HyperlaneMessage, QueueOperation}; + use serde_json::json; use std::net::SocketAddr; use tokio::sync::broadcast::{Receiver, Sender}; - fn setup_test_server() -> (SocketAddr, Receiver) { - let broadcast_tx = Sender::::new(ENDPOINT_MESSAGES_QUEUE_SIZE); + fn setup_test_server() -> (SocketAddr, Receiver) { + let broadcast_tx = Sender::::new(ENDPOINT_MESSAGES_QUEUE_SIZE); let message_retry_api = MessageRetryApi::new(broadcast_tx.clone()); let (path, retry_router) = message_retry_api.get_route(); + let app = Router::new().nest(path, retry_router); // Running the app in the background using a test server @@ -122,49 +65,186 @@ mod tests { async fn test_message_id_retry() { let (addr, mut rx) = setup_test_server(); - // Create a random message ID - let message_id = H256::random(); + let client = reqwest::Client::new(); + // Create a random message with a random message ID + let message = HyperlaneMessage::default(); + let pending_operation = MockPendingOperation::with_message_data(message.clone()); + let matching_list_body = json!([ + { + "messageid": message.id() + } + ]); - // Send a GET request to the server - let response = reqwest::get(format!( - "http://{}{}?message_id={}", - addr, - MESSAGE_RETRY_API_BASE, - message_id.encode_hex::() - )) - .await - .unwrap(); + // Send a POST request to the server + let response = client + .post(format!("http://{}{}", addr, MESSAGE_RETRY_API_BASE)) + .json(&matching_list_body) // Set the request body + .send() + .await + .unwrap(); // Check that the response status code is OK assert_eq!(response.status(), StatusCode::OK); - assert_eq!( - rx.try_recv().unwrap(), - MessageRetryRequest::MessageId(message_id) - ); + let list = rx.try_recv().unwrap(); + // Check that the list received by the server matches the pending operation + assert!(list.op_matches(&(Box::new(pending_operation) as QueueOperation))); } #[tokio::test] async fn test_destination_domain_retry() { let (addr, mut rx) = setup_test_server(); - // Create a random destination domain - let destination_domain = 42; + let client = reqwest::Client::new(); + let mut message = HyperlaneMessage::default(); + // Use a random destination domain + message.destination = 42; + let pending_operation = MockPendingOperation::with_message_data(message.clone()); + let matching_list_body = json!([ + { + "destinationdomain": message.destination + } + ]); + + // Send a POST request to the server + let response = client + .post(format!("http://{}{}", addr, MESSAGE_RETRY_API_BASE)) + .json(&matching_list_body) // Set the request body + .send() + .await + .unwrap(); + + // Check that the response status code is OK + assert_eq!(response.status(), StatusCode::OK); + + let list = rx.try_recv().unwrap(); + // Check that the list received by the server matches the pending operation + assert!(list.op_matches(&(Box::new(pending_operation) as QueueOperation))); + } + + #[tokio::test] + async fn test_origin_domain_retry() { + 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 pending_operation = MockPendingOperation::with_message_data(message.clone()); + let matching_list_body = json!([ + { + "origindomain": message.origin + } + ]); + + // Send a POST request to the server + let response = client + .post(format!("http://{}{}", addr, MESSAGE_RETRY_API_BASE)) + .json(&matching_list_body) // Set the request body + .send() + .await + .unwrap(); + + // Check that the response status code is OK + assert_eq!(response.status(), StatusCode::OK); + + let list = rx.try_recv().unwrap(); + // Check that the list received by the server matches the pending operation + assert!(list.op_matches(&(Box::new(pending_operation) as QueueOperation))); + } + + #[tokio::test] + async fn test_sender_address_retry() { + let (addr, mut rx) = setup_test_server(); + + let client = reqwest::Client::new(); + let message = HyperlaneMessage::default(); + let pending_operation = MockPendingOperation::with_message_data(message.clone()); + let matching_list_body = json!([ + { + "senderaddress": message.sender + } + ]); + + // Send a POST request to the server + let response = client + .post(format!("http://{}{}", addr, MESSAGE_RETRY_API_BASE)) + .json(&matching_list_body) // Set the request body + .send() + .await + .unwrap(); + + // Check that the response status code is OK + assert_eq!(response.status(), StatusCode::OK); + + let list = rx.try_recv().unwrap(); + // Check that the list received by the server matches the pending operation + assert!(list.op_matches(&(Box::new(pending_operation) as QueueOperation))); + } + + #[tokio::test] + async fn test_recipient_address_retry() { + let (addr, mut rx) = setup_test_server(); + + let client = reqwest::Client::new(); + let message = HyperlaneMessage::default(); + let pending_operation = MockPendingOperation::with_message_data(message.clone()); + let matching_list_body = json!([ + { + "recipientaddress": message.recipient + } + ]); + + // Send a POST request to the server + let response = client + .post(format!("http://{}{}", addr, MESSAGE_RETRY_API_BASE)) + .json(&matching_list_body) // Set the request body + .send() + .await + .unwrap(); + + // Check that the response status code is OK + assert_eq!(response.status(), StatusCode::OK); + + let list = rx.try_recv().unwrap(); + // Check that the list received by the server matches the pending operation + assert!(list.op_matches(&(Box::new(pending_operation) as QueueOperation))); + } + + #[tokio::test] + async fn test_multiple_retry() { + 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 pending_operation = MockPendingOperation::with_message_data(message.clone()); + let matching_list_body = json!([ + { + "origindomain": message.origin + }, + { + "destinationdomain": message.destination + }, + { + "messageid": message.id() + } + ]); - // Send a GET request to the server - let response = reqwest::get(format!( - "http://{}{}?destination_domain={}", - addr, MESSAGE_RETRY_API_BASE, destination_domain - )) - .await - .unwrap(); + // Send a POST request to the server + let response = client + .post(format!("http://{}{}", addr, MESSAGE_RETRY_API_BASE)) + .json(&matching_list_body) // Set the request body + .send() + .await + .unwrap(); // Check that the response status code is OK assert_eq!(response.status(), StatusCode::OK); - assert_eq!( - rx.try_recv().unwrap(), - MessageRetryRequest::DestinationDomain(destination_domain) - ); + let list = rx.try_recv().unwrap(); + // Check that the list received by the server matches the pending operation + assert!(list.op_matches(&(Box::new(pending_operation) as QueueOperation))); } } diff --git a/rust/main/agents/relayer/src/server/mod.rs b/rust/main/agents/relayer/src/server/mod.rs index 4b60e7bb54..083f8d94d2 100644 --- a/rust/main/agents/relayer/src/server/mod.rs +++ b/rust/main/agents/relayer/src/server/mod.rs @@ -3,7 +3,7 @@ use derive_new::new; use std::collections::HashMap; use tokio::sync::broadcast::Sender; -use crate::msg::op_queue::OperationPriorityQueue; +use crate::{msg::op_queue::OperationPriorityQueue, settings::matching_list::MatchingList}; pub const ENDPOINT_MESSAGES_QUEUE_SIZE: usize = 100; @@ -16,13 +16,13 @@ mod message_retry; #[derive(new)] pub struct Server { #[new(default)] - retry_transmitter: Option>, + retry_transmitter: Option>, #[new(default)] op_queues: Option>, } impl Server { - pub fn with_op_retry(mut self, transmitter: Sender) -> Self { + pub fn with_op_retry(mut self, transmitter: Sender) -> Self { self.retry_transmitter = Some(transmitter); self } diff --git a/rust/main/agents/relayer/src/settings/matching_list.rs b/rust/main/agents/relayer/src/settings/matching_list.rs index 6424283e37..ca65ed67f5 100644 --- a/rust/main/agents/relayer/src/settings/matching_list.rs +++ b/rust/main/agents/relayer/src/settings/matching_list.rs @@ -8,7 +8,9 @@ use std::{ marker::PhantomData, }; -use hyperlane_core::{config::StrOrInt, utils::hex_or_base58_to_h256, HyperlaneMessage, H256}; +use hyperlane_core::{ + config::StrOrInt, utils::hex_or_base58_to_h256, HyperlaneMessage, QueueOperation, H256, +}; use serde::{ de::{Error, SeqAccess, Visitor}, Deserialize, Deserializer, @@ -223,6 +225,8 @@ impl<'de> Deserialize<'de> for Filter { #[derive(Debug, Deserialize, Clone)] #[serde(tag = "type")] struct ListElement { + #[serde(default, rename = "messageid")] + message_id: Filter, #[serde(default, rename = "origindomain")] origin_domain: Filter, #[serde(default, rename = "senderaddress")] @@ -237,7 +241,8 @@ impl Display for ListElement { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!( f, - "{{originDomain: {}, senderAddress: {}, destinationDomain: {}, recipientAddress: {}}}", + "{{messageId: {}, originDomain: {}, senderAddress: {}, destinationDomain: {}, recipientAddress: {}}}", + self.message_id, self.origin_domain, self.sender_address, self.destination_domain, @@ -248,6 +253,7 @@ impl Display for ListElement { #[derive(Copy, Clone, Debug)] struct MatchInfo<'a> { + src_msg_id: H256, src_domain: u32, src_addr: &'a H256, dst_domain: u32, @@ -257,6 +263,7 @@ struct MatchInfo<'a> { impl<'a> From<&'a HyperlaneMessage> for MatchInfo<'a> { fn from(msg: &'a HyperlaneMessage) -> Self { Self { + src_msg_id: msg.id(), src_domain: msg.origin, src_addr: &msg.sender, dst_domain: msg.destination, @@ -265,13 +272,51 @@ impl<'a> From<&'a HyperlaneMessage> for MatchInfo<'a> { } } +impl<'a> From<&'a QueueOperation> for MatchInfo<'a> { + fn from(op: &'a QueueOperation) -> Self { + Self { + src_msg_id: op.id(), + src_domain: op.origin_domain_id(), + src_addr: op.sender_address(), + dst_domain: op.destination_domain().id(), + dst_addr: op.recipient_address(), + } + } +} + impl MatchingList { + pub fn with_message_id(message_id: H256) -> Self { + Self(Some(vec![ListElement { + message_id: Filter::Enumerated(vec![message_id]), + origin_domain: Default::default(), + sender_address: Default::default(), + destination_domain: Default::default(), + recipient_address: Default::default(), + }])) + } + + pub fn with_destination_domain(destination_domain: u32) -> Self { + Self(Some(vec![ListElement { + message_id: Default::default(), + origin_domain: Default::default(), + sender_address: Default::default(), + destination_domain: Filter::Enumerated(vec![destination_domain]), + recipient_address: Default::default(), + }])) + } + /// Check if a message matches any of the rules. /// - `default`: What to return if the matching list is empty. pub fn msg_matches(&self, msg: &HyperlaneMessage, default: bool) -> bool { self.matches(msg.into(), default) } + /// Check if queue operation matches any of the rules. + /// If the matching list is empty, we assume the queue operation does not match. + pub fn op_matches(&self, op: &QueueOperation) -> bool { + self.matches(op.into(), false) + } + /// Check if a message matches any of the rules. /// - `default`: What to return if the matching list is empty. fn matches(&self, info: MatchInfo, default: bool) -> bool { @@ -285,7 +330,8 @@ impl MatchingList { fn matches_any_rule<'a>(mut rules: impl Iterator, info: MatchInfo) -> bool { rules.any(|rule| { - rule.origin_domain.matches(&info.src_domain) + rule.message_id.matches(&info.src_msg_id) + && rule.origin_domain.matches(&info.src_domain) && rule.sender_address.matches(info.src_addr) && rule.destination_domain.matches(&info.dst_domain) && rule.recipient_address.matches(info.dst_addr) @@ -323,23 +369,26 @@ mod test { #[test] fn basic_config() { - let list: MatchingList = serde_json::from_str(r#"[{"origindomain": "*", "senderaddress": "*", "destinationdomain": "*", "recipientaddress": "*"}, {}]"#).unwrap(); + let list: MatchingList = serde_json::from_str(r#"[{"messageid": "*", "origindomain": "*", "senderaddress": "*", "destinationdomain": "*", "recipientaddress": "*"}, {}]"#).unwrap(); assert!(list.0.is_some()); assert_eq!(list.0.as_ref().unwrap().len(), 2); let elem = &list.0.as_ref().unwrap()[0]; assert_eq!(elem.destination_domain, Wildcard); + assert_eq!(elem.message_id, Wildcard); assert_eq!(elem.recipient_address, Wildcard); assert_eq!(elem.origin_domain, Wildcard); assert_eq!(elem.sender_address, Wildcard); let elem = &list.0.as_ref().unwrap()[1]; assert_eq!(elem.destination_domain, Wildcard); + assert_eq!(elem.message_id, Wildcard); assert_eq!(elem.recipient_address, Wildcard); assert_eq!(elem.origin_domain, Wildcard); assert_eq!(elem.sender_address, Wildcard); assert!(list.matches( MatchInfo { + src_msg_id: H256::random(), src_domain: 0, src_addr: &H256::default(), dst_domain: 0, @@ -350,6 +399,7 @@ mod test { assert!(list.matches( MatchInfo { + src_msg_id: H256::random(), src_domain: 34, src_addr: &"0x9d4454B023096f34B160D6B654540c56A1F81688" .parse::() @@ -369,6 +419,7 @@ mod test { assert_eq!(list.0.as_ref().unwrap().len(), 1); let elem = &list.0.as_ref().unwrap()[0]; assert_eq!(elem.destination_domain, Wildcard); + assert_eq!(elem.message_id, Wildcard); assert_eq!( elem.recipient_address, Enumerated(vec!["0x9d4454B023096f34B160D6B654540c56A1F81688" @@ -387,6 +438,7 @@ mod test { assert!(list.matches( MatchInfo { + src_msg_id: H256::default(), src_domain: 34, src_addr: &"0x9d4454B023096f34B160D6B654540c56A1F81688" .parse::() @@ -403,6 +455,7 @@ mod test { assert!(!list.matches( MatchInfo { + src_msg_id: H256::default(), src_domain: 34, src_addr: &"0x9d4454B023096f34B160D6B654540c56A1F81688" .parse::() @@ -423,6 +476,7 @@ mod test { assert_eq!(whitelist.0.as_ref().unwrap().len(), 1); let elem = &whitelist.0.as_ref().unwrap()[0]; assert_eq!(elem.destination_domain, Enumerated(vec![9913372, 9913373])); + assert_eq!(elem.message_id, Wildcard); assert_eq!(elem.recipient_address, Wildcard); assert_eq!(elem.origin_domain, Wildcard); assert_eq!(elem.sender_address, Wildcard); @@ -437,6 +491,7 @@ mod test { #[test] fn matches_empty_list() { let info = MatchInfo { + src_msg_id: H256::default(), src_domain: 0, src_addr: &H256::default(), dst_domain: 0, @@ -451,7 +506,7 @@ mod test { #[test] fn supports_base58() { serde_json::from_str::( - r#"[{"origindomain":1399811151,"senderaddress":"DdTMkk9nuqH5LnD56HLkPiKMV3yB3BNEYSQfgmJHa5i7","destinationdomain":11155111,"recipientaddress":"0x6AD4DEBA8A147d000C09de6465267a9047d1c217"}]"#, + r#"[{"messageid": "*", "origindomain":1399811151,"senderaddress":"DdTMkk9nuqH5LnD56HLkPiKMV3yB3BNEYSQfgmJHa5i7","destinationdomain":11155111,"recipientaddress":"0x6AD4DEBA8A147d000C09de6465267a9047d1c217"}]"#, ).unwrap(); } diff --git a/rust/main/hyperlane-core/src/traits/pending_operation.rs b/rust/main/hyperlane-core/src/traits/pending_operation.rs index f5480b197b..e0a6622c3a 100644 --- a/rust/main/hyperlane-core/src/traits/pending_operation.rs +++ b/rust/main/hyperlane-core/src/traits/pending_operation.rs @@ -61,6 +61,12 @@ pub trait PendingOperation: Send + Sync + Debug + TryBatchAs { /// The domain this operation will take place on. fn destination_domain(&self) -> &HyperlaneDomain; + /// The sender address of this operation. + fn sender_address(&self) -> &H256; + + /// The recipient address of this operation. + fn recipient_address(&self) -> &H256; + /// Label to use for metrics granularity. fn app_context(&self) -> Option; From 8dbc2e636ef61ae8d351f0939e3cdaca2c78bcbb Mon Sep 17 00:00:00 2001 From: Daniel Savu <23065004+daniel-savu@users.noreply.github.com> Date: Wed, 30 Oct 2024 12:46:41 +0000 Subject: [PATCH 47/61] fix: another sealevel unit test flake (#4784) Applies the same fix from https://github.com/hyperlane-xyz/hyperlane-monorepo/pull/4655 to a flake that showed up in this PRs CI: https://github.com/hyperlane-xyz/hyperlane-monorepo/pull/4693#event-15004545304 --- rust/sealevel/programs/mailbox-test/src/functional.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/rust/sealevel/programs/mailbox-test/src/functional.rs b/rust/sealevel/programs/mailbox-test/src/functional.rs index 3c4ace00ea..0870ef2fa4 100644 --- a/rust/sealevel/programs/mailbox-test/src/functional.rs +++ b/rust/sealevel/programs/mailbox-test/src/functional.rs @@ -1,3 +1,5 @@ +use std::thread::sleep; + use borsh::BorshDeserialize; use hyperlane_core::{ accumulator::incremental::IncrementalMerkle as MerkleTree, HyperlaneMessage, H256, @@ -976,6 +978,10 @@ async fn test_process_errors_if_message_already_processed() { .await .unwrap(); + // there's a race condition that isn't fixed by setting `CommitmentLevel::Confirmed` + // just wait a bit to ensure the message is processed + sleep(std::time::Duration::from_secs(1)); + let result = process( &mut banks_client, &payer, From 1b5bfa3466f290745fbf9ac751fa85e3152edbe9 Mon Sep 17 00:00:00 2001 From: Trevor Porter Date: Wed, 30 Oct 2024 13:18:13 +0000 Subject: [PATCH 48/61] feat: add a script for getting typical remote gas amounts (#4782) ### Description - Sometimes Jake requests an update on these for his own models, this makes it easier for us to get it for him ### Drive-by changes ### Related issues ### Backward compatibility ### Testing --- .../infra/config/environments/mainnet3/igp.ts | 4 +- .../scripts/get-typical-remote-gas-amounts.ts | 49 +++++++++++++++++++ typescript/infra/src/config/gas-oracle.ts | 18 ++++++- 3 files changed, 68 insertions(+), 3 deletions(-) create mode 100644 typescript/infra/scripts/get-typical-remote-gas-amounts.ts diff --git a/typescript/infra/config/environments/mainnet3/igp.ts b/typescript/infra/config/environments/mainnet3/igp.ts index fa4ab8e919..2b388afdd2 100644 --- a/typescript/infra/config/environments/mainnet3/igp.ts +++ b/typescript/infra/config/environments/mainnet3/igp.ts @@ -23,13 +23,13 @@ import rawTokenPrices from './tokenPrices.json'; const tokenPrices: ChainMap = rawTokenPrices; -const getOverheadWithOverrides = (local: ChainName, remote: ChainName) => { +export function getOverheadWithOverrides(local: ChainName, remote: ChainName) { let overhead = getOverhead(local, remote, ethereumChainNames); if (remote === 'moonbeam') { overhead *= 4; } return overhead; -}; +} const storageGasOracleConfig: AllStorageGasOracleConfigs = getAllStorageGasOracleConfigs( diff --git a/typescript/infra/scripts/get-typical-remote-gas-amounts.ts b/typescript/infra/scripts/get-typical-remote-gas-amounts.ts new file mode 100644 index 0000000000..5f190a72dd --- /dev/null +++ b/typescript/infra/scripts/get-typical-remote-gas-amounts.ts @@ -0,0 +1,49 @@ +import { ChainMap } from '@hyperlane-xyz/sdk'; +import { stringifyObject } from '@hyperlane-xyz/utils'; + +import { getOverheadWithOverrides } from '../config/environments/mainnet3/igp.js'; +import { getTypicalRemoteGasAmount } from '../src/config/gas-oracle.js'; + +import { getArgs } from './agent-utils.js'; +import { getEnvironmentConfig } from './core-utils.js'; + +// This script exists to print the typical local -> remote gas amounts for a given environment. +// This is useful for Jake to use in his own models for assessing message costs. + +async function main() { + const args = await getArgs().argv; + + if (args.environment !== 'mainnet3') { + throw new Error('This script only supports the mainnet3 environment'); + } + + const environmentConfig = getEnvironmentConfig(args.environment); + + // Local -> Remote -> Amount of gas. + // Local is important because depending on the validator threshold, the cost + // to verify changes. Remote is important because the cost to execute the + // message can change depending on the chain (e.g. alt VMs, or some exceptions like Moonbeam + // that has non-standard EVM gas usage). + const amounts: ChainMap> = {}; + + for (const local of environmentConfig.supportedChainNames) { + for (const remote of environmentConfig.supportedChainNames) { + if (local === remote) { + continue; + } + amounts[local] = amounts[local] || {}; + amounts[local][remote] = getTypicalRemoteGasAmount( + local, + remote, + getOverheadWithOverrides, + ); + } + } + + console.log(stringifyObject(amounts, 'json', 2)); +} + +main().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/typescript/infra/src/config/gas-oracle.ts b/typescript/infra/src/config/gas-oracle.ts index c040ff45bb..622ad18b36 100644 --- a/typescript/infra/src/config/gas-oracle.ts +++ b/typescript/infra/src/config/gas-oracle.ts @@ -22,6 +22,10 @@ export type AllStorageGasOracleConfigs = ChainMap< // Overcharge by 50% to account for market making risk export const EXCHANGE_RATE_MARGIN_PCT = 50; +// Arbitrarily chosen as a typical amount of gas used in a message's handle function. +// Used for determining typical gas costs for a message. +export const TYPICAL_HANDLE_GAS_USAGE = 50_000; + // Gets the StorageGasOracleConfig for each remote chain for a particular local chain. // Accommodates small non-integer gas prices by scaling up the gas price // and scaling down the exchange rate by the same factor. @@ -81,7 +85,11 @@ function getLocalStorageGasOracleConfigOverride( // If we have access to these, let's use the USD prices to apply some minimum // typical USD payment heuristics. if (getTokenUsdPrice && getOverhead) { - const typicalRemoteGasAmount = getOverhead(local, remote) + 50_000; + const typicalRemoteGasAmount = getTypicalRemoteGasAmount( + local, + remote, + getOverhead, + ); const typicalIgpQuoteUsd = getUsdQuote( local, gasPriceBn, @@ -112,6 +120,14 @@ function getLocalStorageGasOracleConfigOverride( }, {}); } +export function getTypicalRemoteGasAmount( + local: ChainName, + remote: ChainName, + getOverhead: (local: ChainName, remote: ChainName) => number, +): number { + return getOverhead(local, remote) + TYPICAL_HANDLE_GAS_USAGE; +} + function getMinUsdCost(local: ChainName, remote: ChainName): number { // By default, min cost is 20 cents let minUsdCost = 0.2; From 7e9e248beff122e7f1093eb8883b4ffe9f04a919 Mon Sep 17 00:00:00 2001 From: Lee <6251863+ltyu@users.noreply.github.com> Date: Wed, 30 Oct 2024 11:45:46 -0400 Subject: [PATCH 49/61] feat: allow update destination gas (#4674) ### Description Updates destination gas on all chain by getting the value from warpConfig.gas. It then synchronizes all destination gas amounts across all chains. ### Related issues - Fixes #4529 ### Backward compatibility Yes ### Testing Manual/e2e --- .changeset/long-queens-deny.md | 6 + .changeset/tidy-meals-add.md | 2 +- typescript/cli/src/deploy/warp.ts | 151 ++++++++++++------ .../cli/src/tests/warp-apply.e2e-test.ts | 56 +++++++ typescript/sdk/src/index.ts | 1 + typescript/sdk/src/router/schemas.ts | 7 + typescript/sdk/src/router/types.ts | 2 + .../token/EvmERC20WarpModule.hardhat-test.ts | 34 ++++ .../sdk/src/token/EvmERC20WarpModule.ts | 62 +++++++ .../sdk/src/token/EvmERC20WarpRouteReader.ts | 28 +++- 10 files changed, 301 insertions(+), 48 deletions(-) create mode 100644 .changeset/long-queens-deny.md diff --git a/.changeset/long-queens-deny.md b/.changeset/long-queens-deny.md new file mode 100644 index 0000000000..9ffa67201a --- /dev/null +++ b/.changeset/long-queens-deny.md @@ -0,0 +1,6 @@ +--- +'@hyperlane-xyz/cli': minor +'@hyperlane-xyz/sdk': minor +--- + +Add feat to allow updates to destination gas using warp apply diff --git a/.changeset/tidy-meals-add.md b/.changeset/tidy-meals-add.md index 47b2fee5ad..2d1ba70ca5 100644 --- a/.changeset/tidy-meals-add.md +++ b/.changeset/tidy-meals-add.md @@ -1,5 +1,5 @@ --- -"@hyperlane-xyz/utils": patch +'@hyperlane-xyz/utils': patch --- Filter undefined/null values in invertKeysAndValues function diff --git a/typescript/cli/src/deploy/warp.ts b/typescript/cli/src/deploy/warp.ts index 36bbc2ad8f..38a5e14f27 100644 --- a/typescript/cli/src/deploy/warp.ts +++ b/typescript/cli/src/deploy/warp.ts @@ -12,6 +12,7 @@ import { ChainSubmissionStrategy, ChainSubmissionStrategySchema, ContractVerifier, + DestinationGas, EvmERC20WarpModule, EvmERC20WarpRouteReader, EvmIsmModule, @@ -54,7 +55,6 @@ import { Address, ProtocolType, assert, - isObjEmpty, objFilter, objKeys, objMap, @@ -474,7 +474,6 @@ async function extendWarpRoute( warpDeployConfig: WarpRouteDeployConfig, warpCoreConfigByChain: ChainMap, ) { - logBlue('Extending Warp Route'); const { multiProvider } = params.context; const warpCoreChains = Object.keys(warpCoreConfigByChain); @@ -489,7 +488,10 @@ async function extendWarpRoute( (chain, _config): _config is any => !warpCoreChains.includes(chain), ); - if (isObjEmpty(extendedConfigs)) return []; + const extendedChains = Object.keys(extendedConfigs); + if (extendedChains.length === 0) return []; + + logBlue(`Extending Warp Route to ${extendedChains.join(', ')}`); extendedConfigs = await deriveMetadataFromExisting( multiProvider, @@ -536,28 +538,33 @@ async function updateExistingWarpRoute( ExplorerLicenseType.MIT, ); const transactions: AnnotatedEV5Transaction[] = []; + await promiseObjAll( objMap(warpDeployConfig, async (chain, config) => { - const deployedConfig = warpCoreConfigByChain[chain]; - if (!deployedConfig) - return logGray( - `Missing artifacts for ${chain}. Probably new deployment. Skipping update...`, - ); - config.ismFactoryAddresses = addresses[ - chain - ] as ProxyFactoryFactoriesAddresses; - const evmERC20WarpModule = new EvmERC20WarpModule( - multiProvider, - { - config, - chain, - addresses: { - deployedTokenRoute: deployedConfig.addressOrDenom!, + await retryAsync(async () => { + logGray(`Update existing warp route for chain ${chain}`); + const deployedConfig = warpCoreConfigByChain[chain]; + if (!deployedConfig) + return logGray( + `Missing artifacts for ${chain}. Probably new deployment. Skipping update...`, + ); + + config.ismFactoryAddresses = addresses[ + chain + ] as ProxyFactoryFactoriesAddresses; + const evmERC20WarpModule = new EvmERC20WarpModule( + multiProvider, + { + config, + chain, + addresses: { + deployedTokenRoute: deployedConfig.addressOrDenom!, + }, }, - }, - contractVerifier, - ); - transactions.push(...(await evmERC20WarpModule.update(config))); + contractVerifier, + ); + transactions.push(...(await evmERC20WarpModule.update(config))); + }); }), ); return transactions; @@ -636,11 +643,17 @@ async function enrollRemoteRouters( ): Promise { logBlue(`Enrolling deployed routers with each other...`); const { multiProvider } = params.context; - const deployedRouters: ChainMap
= objMap( + const deployedRoutersAddresses: ChainMap
= objMap( deployedContractsMap, (_, contracts) => getRouter(contracts).address, ); - const allChains = Object.keys(deployedRouters); + const deployedDestinationGas: DestinationGas = await populateDestinationGas( + multiProvider, + params.warpDeployConfig, + deployedContractsMap, + ); + + const deployedChains = Object.keys(deployedRoutersAddresses); const transactions: AnnotatedEV5Transaction[] = []; await promiseObjAll( objMap(deployedContractsMap, async (chain, contracts) => { @@ -662,14 +675,23 @@ async function enrollRemoteRouters( const otherChains = multiProvider .getRemoteChains(chain) - .filter((c) => allChains.includes(c)); + .filter((c) => deployedChains.includes(c)); mutatedWarpRouteConfig.remoteRouters = - otherChains.reduce((remoteRouters, chain) => { - remoteRouters[multiProvider.getDomainId(chain)] = - deployedRouters[chain]; + otherChains.reduce((remoteRouters, otherChain) => { + remoteRouters[multiProvider.getDomainId(otherChain)] = + deployedRoutersAddresses[otherChain]; return remoteRouters; }, {}); + + mutatedWarpRouteConfig.destinationGas = + otherChains.reduce((destinationGas, otherChain) => { + const otherChainDomain = multiProvider.getDomainId(otherChain); + destinationGas[otherChainDomain] = + deployedDestinationGas[otherChainDomain]; + return destinationGas; + }, {}); + const mutatedConfigTxs: AnnotatedEV5Transaction[] = await evmERC20WarpModule.update(mutatedWarpRouteConfig); @@ -685,6 +707,38 @@ async function enrollRemoteRouters( return transactions; } +/** + * Populates the destination gas amounts for each chain using warpConfig.gas OR querying other router's destinationGas + */ +async function populateDestinationGas( + multiProvider: MultiProvider, + warpDeployConfig: WarpRouteDeployConfig, + deployedContractsMap: HyperlaneContractsMap, +): Promise { + const destinationGas: DestinationGas = {}; + const deployedChains = Object.keys(deployedContractsMap); + await promiseObjAll( + objMap(deployedContractsMap, async (chain, contracts) => { + await retryAsync(async () => { + const router = getRouter(contracts); + + const otherChains = multiProvider + .getRemoteChains(chain) + .filter((c) => deployedChains.includes(c)); + + for (const otherChain of otherChains) { + const otherDomain = multiProvider.getDomainId(otherChain); + if (!destinationGas[otherDomain]) + destinationGas[otherDomain] = + warpDeployConfig[otherChain].gas?.toString() || + (await router.destinationGas(otherDomain)).toString(); + } + }); + }), + ); + return destinationGas; +} + function getRouter(contracts: HyperlaneContracts) { for (const key of objKeys(hypERC20factories)) { if (contracts[key]) return contracts[key]; @@ -830,24 +884,29 @@ async function submitWarpApplyTransactions( const { multiProvider } = params.context; await promiseObjAll( objMap(chainTransactions, async (chainId, transactions) => { - const chain = multiProvider.getChainName(chainId); - const submitter: TxSubmitterBuilder = - await getWarpApplySubmitter({ - chain, - context: params.context, - strategyUrl: params.strategyUrl, - }); - - const transactionReceipts = await submitter.submit(...transactions); - if (transactionReceipts) { - const receiptPath = `${params.receiptsDir}/${chain}-${ - submitter.txSubmitterType - }-${Date.now()}-receipts.json`; - writeYamlOrJson(receiptPath, transactionReceipts); - logGreen( - `Transactions receipts successfully written to ${receiptPath}`, - ); - } + await retryAsync( + async () => { + const chain = multiProvider.getChainName(chainId); + const submitter: TxSubmitterBuilder = + await getWarpApplySubmitter({ + chain, + context: params.context, + strategyUrl: params.strategyUrl, + }); + const transactionReceipts = await submitter.submit(...transactions); + if (transactionReceipts) { + const receiptPath = `${params.receiptsDir}/${chain}-${ + submitter.txSubmitterType + }-${Date.now()}-receipts.json`; + writeYamlOrJson(receiptPath, transactionReceipts); + logGreen( + `Transactions receipts successfully written to ${receiptPath}`, + ); + } + }, + 5, // attempts + 100, // baseRetryMs + ); }), ); } diff --git a/typescript/cli/src/tests/warp-apply.e2e-test.ts b/typescript/cli/src/tests/warp-apply.e2e-test.ts index 2346038d66..ff891448d1 100644 --- a/typescript/cli/src/tests/warp-apply.e2e-test.ts +++ b/typescript/cli/src/tests/warp-apply.e2e-test.ts @@ -259,4 +259,60 @@ describe('WarpApply e2e tests', async function () { expect(remoteRouterKeys2).to.include(chain3Id); expect(remoteRouterKeys3).to.include(chain2Id); }); + + it('should extend an existing warp route and update all destination domains', async () => { + // Read existing config into a file + const warpConfigPath = `${TEMP_PATH}/warp-route-deployment-2.yaml`; + const warpDeployConfig = await readWarpConfig( + CHAIN_NAME_2, + WARP_CORE_CONFIG_PATH_2, + warpConfigPath, + ); + warpDeployConfig[CHAIN_NAME_2].gas = 7777; + + // Extend with new config + const GAS = 694200; + const extendedConfig: TokenRouterConfig = { + decimals: 18, + mailbox: chain2Addresses!.mailbox, + name: 'Ether', + owner: new Wallet(ANVIL_KEY).address, + symbol: 'ETH', + totalSupply: 0, + type: TokenType.native, + gas: GAS, + }; + warpDeployConfig[CHAIN_NAME_3] = extendedConfig; + writeYamlOrJson(warpConfigPath, warpDeployConfig); + await hyperlaneWarpApply(warpConfigPath, WARP_CORE_CONFIG_PATH_2); + + const COMBINED_WARP_CORE_CONFIG_PATH = `${REGISTRY_PATH}/deployments/warp_routes/ETH/anvil2-anvil3-config.yaml`; + + // Check that chain2 is enrolled in chain1 + const updatedWarpDeployConfig_2 = await readWarpConfig( + CHAIN_NAME_2, + COMBINED_WARP_CORE_CONFIG_PATH, + warpConfigPath, + ); + + const chain2Id = await getChainId(CHAIN_NAME_2, ANVIL_KEY); + const chain3Id = await getChainId(CHAIN_NAME_3, ANVIL_KEY); + + // Destination gas should be set in the existing chain (chain2) to include the extended chain (chain3) + const destinationGas_2 = + updatedWarpDeployConfig_2[CHAIN_NAME_2].destinationGas!; + expect(Object.keys(destinationGas_2)).to.include(chain3Id); + expect(destinationGas_2[chain3Id]).to.equal(GAS.toString()); + + // Destination gas should be set for the extended chain (chain3) + const updatedWarpDeployConfig_3 = await readWarpConfig( + CHAIN_NAME_3, + COMBINED_WARP_CORE_CONFIG_PATH, + warpConfigPath, + ); + const destinationGas_3 = + updatedWarpDeployConfig_3[CHAIN_NAME_3].destinationGas!; + expect(Object.keys(destinationGas_3)).to.include(chain2Id); + expect(destinationGas_3[chain2Id]).to.equal('7777'); + }); }); diff --git a/typescript/sdk/src/index.ts b/typescript/sdk/src/index.ts index 5ab4630b99..efadabc449 100644 --- a/typescript/sdk/src/index.ts +++ b/typescript/sdk/src/index.ts @@ -394,6 +394,7 @@ export { ProxiedFactories, ProxiedRouterConfig, RemoteRouters, + DestinationGas, RouterAddress, RouterConfig, RouterViolation, diff --git a/typescript/sdk/src/router/schemas.ts b/typescript/sdk/src/router/schemas.ts index abccd91610..d49a72dbd2 100644 --- a/typescript/sdk/src/router/schemas.ts +++ b/typescript/sdk/src/router/schemas.ts @@ -32,6 +32,13 @@ export const RouterConfigSchema = MailboxClientConfigSchema.merge( }), ); +const DestinationGasDomain = z.string(); +const DestinationGasAmount = z.string(); // This must be a string type to match Ether's type +export const DestinationGasSchema = z.record( + DestinationGasDomain, + DestinationGasAmount, +); export const GasRouterConfigSchema = RouterConfigSchema.extend({ gas: z.number().optional(), + destinationGas: DestinationGasSchema.optional(), }); diff --git a/typescript/sdk/src/router/types.ts b/typescript/sdk/src/router/types.ts index 2b0c81de56..17e36ead66 100644 --- a/typescript/sdk/src/router/types.ts +++ b/typescript/sdk/src/router/types.ts @@ -14,6 +14,7 @@ import { CheckerViolation } from '../deploy/types.js'; import { ChainMap } from '../types.js'; import { + DestinationGasSchema, GasRouterConfigSchema, MailboxClientConfigSchema, RemoteRoutersSchema, @@ -66,3 +67,4 @@ export interface RouterViolation extends CheckerViolation { } export type RemoteRouters = z.infer; +export type DestinationGas = z.infer; diff --git a/typescript/sdk/src/token/EvmERC20WarpModule.hardhat-test.ts b/typescript/sdk/src/token/EvmERC20WarpModule.hardhat-test.ts index 467a150ad5..33b9bdaa47 100644 --- a/typescript/sdk/src/token/EvmERC20WarpModule.hardhat-test.ts +++ b/typescript/sdk/src/token/EvmERC20WarpModule.hardhat-test.ts @@ -518,5 +518,39 @@ describe('EvmERC20WarpHyperlaneModule', async () => { }); expect(txs.length).to.equal(0); }); + + it('should update the destination gas', async () => { + const domain = 3; + const config: TokenRouterConfig = { + ...baseConfig, + type: TokenType.native, + hook: hookAddress, + ismFactoryAddresses, + remoteRouters: { + [domain]: randomAddress(), + }, + }; + + // Deploy using WarpModule + const evmERC20WarpModule = await EvmERC20WarpModule.create({ + chain, + config: { + ...config, + }, + multiProvider, + }); + await sendTxs( + await evmERC20WarpModule.update({ + ...config, + destinationGas: { + [domain]: '5000', + }, + }), + ); + + const updatedConfig = await evmERC20WarpModule.read(); + expect(Object.keys(updatedConfig.destinationGas!).length).to.be.equal(1); + expect(updatedConfig.destinationGas![domain]).to.equal('5000'); + }); }); }); diff --git a/typescript/sdk/src/token/EvmERC20WarpModule.ts b/typescript/sdk/src/token/EvmERC20WarpModule.ts index 6dec4b093b..462a5b6469 100644 --- a/typescript/sdk/src/token/EvmERC20WarpModule.ts +++ b/typescript/sdk/src/token/EvmERC20WarpModule.ts @@ -1,4 +1,7 @@ +import { BigNumberish } from 'ethers'; + import { + GasRouter__factory, MailboxClient__factory, TokenRouter__factory, } from '@hyperlane-xyz/core'; @@ -12,6 +15,7 @@ import { assert, deepEquals, isObjEmpty, + objMap, rootLogger, } from '@hyperlane-xyz/utils'; @@ -93,9 +97,16 @@ export class EvmERC20WarpModule extends HyperlaneModule< const transactions = []; + /** + * @remark + * The order of operations matter + * 1. createOwnershipUpdateTxs() must always be LAST because no updates possible after ownership transferred + * 2. createRemoteRoutersUpdateTxs() must always be BEFORE createSetDestinationGasUpdateTxs() because gas enumeration depends on domains + */ transactions.push( ...(await this.createIsmUpdateTxs(actualConfig, expectedConfig)), ...this.createRemoteRoutersUpdateTxs(actualConfig, expectedConfig), + ...this.createSetDestinationGasUpdateTxs(actualConfig, expectedConfig), ...this.createOwnershipUpdateTxs(actualConfig, expectedConfig), ); @@ -153,6 +164,57 @@ export class EvmERC20WarpModule extends HyperlaneModule< return updateTransactions; } + /** + * Create a transaction to update the remote routers for the Warp Route contract. + * + * @param actualConfig - The on-chain router configuration, including the remoteRouters array. + * @param expectedConfig - The expected token router configuration. + * @returns A array with a single Ethereum transaction that need to be executed to enroll the routers + */ + createSetDestinationGasUpdateTxs( + actualConfig: TokenRouterConfig, + expectedConfig: TokenRouterConfig, + ): AnnotatedEV5Transaction[] { + const updateTransactions: AnnotatedEV5Transaction[] = []; + if (!expectedConfig.destinationGas) { + return []; + } + + assert(actualConfig.destinationGas, 'actualDestinationGas is undefined'); + assert(expectedConfig.destinationGas, 'actualDestinationGas is undefined'); + + const { destinationGas: actualDestinationGas } = actualConfig; + const { destinationGas: expectedDestinationGas } = expectedConfig; + + if (!deepEquals(actualDestinationGas, expectedDestinationGas)) { + const contractToUpdate = GasRouter__factory.connect( + this.args.addresses.deployedTokenRoute, + this.multiProvider.getProvider(this.domainId), + ); + + // Convert { 1: 2, 2: 3, ... } to [{ 1: 2 }, { 2: 3 }] + const gasRouterConfigs: { domain: BigNumberish; gas: BigNumberish }[] = + []; + objMap(expectedDestinationGas, (domain: string, gas: string) => { + gasRouterConfigs.push({ + domain, + gas, + }); + }); + + updateTransactions.push({ + annotation: `Setting destination gas for ${this.args.addresses.deployedTokenRoute} on ${this.args.chain}`, + chainId: this.domainId, + to: contractToUpdate.address, + data: contractToUpdate.interface.encodeFunctionData( + 'setDestinationGas((uint32,uint256)[])', + [gasRouterConfigs], + ), + }); + } + return updateTransactions; + } + /** * Create transactions to update an existing ISM config, or deploy a new ISM and return a tx to setInterchainSecurityModule * diff --git a/typescript/sdk/src/token/EvmERC20WarpRouteReader.ts b/typescript/sdk/src/token/EvmERC20WarpRouteReader.ts index 8ffbe82cc1..0be06f7ca5 100644 --- a/typescript/sdk/src/token/EvmERC20WarpRouteReader.ts +++ b/typescript/sdk/src/token/EvmERC20WarpRouteReader.ts @@ -25,7 +25,7 @@ import { DEFAULT_CONTRACT_READ_CONCURRENCY } from '../consts/concurrency.js'; import { EvmHookReader } from '../hook/EvmHookReader.js'; import { EvmIsmReader } from '../ism/EvmIsmReader.js'; import { MultiProvider } from '../providers/MultiProvider.js'; -import { RemoteRouters } from '../router/types.js'; +import { DestinationGas, RemoteRouters } from '../router/types.js'; import { ChainNameOrId } from '../types.js'; import { HyperlaneReader } from '../utils/HyperlaneReader.js'; @@ -64,11 +64,13 @@ export class EvmERC20WarpRouteReader extends HyperlaneReader { const baseMetadata = await this.fetchMailboxClientConfig(warpRouteAddress); const tokenMetadata = await this.fetchTokenMetadata(type, warpRouteAddress); const remoteRouters = await this.fetchRemoteRouters(warpRouteAddress); + const destinationGas = await this.fetchDestinationGas(warpRouteAddress); return { ...baseMetadata, ...tokenMetadata, remoteRouters, + destinationGas, type, } as TokenRouterConfig; } @@ -245,4 +247,28 @@ export class EvmERC20WarpRouteReader extends HyperlaneReader { ), ); } + + async fetchDestinationGas( + warpRouteAddress: Address, + ): Promise { + const warpRoute = TokenRouter__factory.connect( + warpRouteAddress, + this.provider, + ); + + /** + * @remark + * Router.domains() is used to enumerate the destination gas because GasRouter.destinationGas is not EnumerableMapExtended type + * This means that if a domain is removed, then we cannot read the destinationGas for it. This may impact updates. + */ + const domains = await warpRoute.domains(); + + return Object.fromEntries( + await Promise.all( + domains.map(async (domain) => { + return [domain, (await warpRoute.destinationGas(domain)).toString()]; + }), + ), + ); + } } From c87cfbd51298eba41da1e57ca3a8c9f6e1d4edc3 Mon Sep 17 00:00:00 2001 From: Danil Nemirovsky Date: Wed, 30 Oct 2024 17:57:47 +0000 Subject: [PATCH 50/61] feat: Scrape Sealevel dispatched messages (#4776) ### Description Scraper is able to index dispatch messages: 1. Blocks are stored into database 2. Transactions are stored into database (need population of all fields) 3. Dispatched messages are stored into database ### Drive-by changes Initial indexing of delivered messages (so that Scraper does not crush) ### Related issues - Contributes into https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/4272 ### Backward compatibility Yes (Solana-like chains should not be enabled for Scraper) ### Testing Manual run of Scraper E2E Tests --------- Co-authored-by: Danil Nemirovsky <4614623+ameten@users.noreply.github.com> --- .../chains/hyperlane-sealevel/src/account.rs | 68 ++++ .../chains/hyperlane-sealevel/src/error.rs | 18 +- .../hyperlane-sealevel/src/interchain_gas.rs | 114 +++--- .../main/chains/hyperlane-sealevel/src/lib.rs | 3 + .../chains/hyperlane-sealevel/src/mailbox.rs | 231 +++++++----- .../chains/hyperlane-sealevel/src/provider.rs | 73 +++- .../hyperlane-sealevel/src/rpc/client.rs | 39 ++- .../hyperlane-sealevel/src/transaction.rs | 188 ++++++++++ .../src/transaction/tests.rs | 329 ++++++++++++++++++ .../chains/hyperlane-sealevel/src/utils.rs | 33 ++ 10 files changed, 916 insertions(+), 180 deletions(-) create mode 100644 rust/main/chains/hyperlane-sealevel/src/account.rs create mode 100644 rust/main/chains/hyperlane-sealevel/src/transaction.rs create mode 100644 rust/main/chains/hyperlane-sealevel/src/transaction/tests.rs create mode 100644 rust/main/chains/hyperlane-sealevel/src/utils.rs diff --git a/rust/main/chains/hyperlane-sealevel/src/account.rs b/rust/main/chains/hyperlane-sealevel/src/account.rs new file mode 100644 index 0000000000..f671c432e8 --- /dev/null +++ b/rust/main/chains/hyperlane-sealevel/src/account.rs @@ -0,0 +1,68 @@ +use base64::{engine::general_purpose::STANDARD as Base64, Engine}; +use solana_account_decoder::{UiAccountEncoding, UiDataSliceConfig}; +use solana_client::{ + rpc_config::{RpcAccountInfoConfig, RpcProgramAccountsConfig}, + rpc_filter::{Memcmp, MemcmpEncodedBytes, RpcFilterType}, +}; +use solana_sdk::{account::Account, commitment_config::CommitmentConfig, pubkey::Pubkey}; + +use hyperlane_core::{ChainCommunicationError, ChainResult}; + +use crate::rpc::SealevelRpcClient; + +pub async fn search_accounts_by_discriminator( + client: &SealevelRpcClient, + program_id: &Pubkey, + discriminator: &[u8; 8], + nonce_bytes: &[u8], + offset: usize, + length: usize, +) -> ChainResult> { + let target_message_account_bytes = &[discriminator, nonce_bytes].concat(); + let target_message_account_bytes = Base64.encode(target_message_account_bytes); + + // First, find all accounts with the matching account data. + // To keep responses small in case there is ever more than 1 + // match, we don't request the full account data, and just request + // the field which was used to generate account id + #[allow(deprecated)] + let memcmp = RpcFilterType::Memcmp(Memcmp { + // Ignore the first byte, which is the `initialized` bool flag. + offset: 1, + bytes: MemcmpEncodedBytes::Base64(target_message_account_bytes), + encoding: None, + }); + let config = RpcProgramAccountsConfig { + filters: Some(vec![memcmp]), + account_config: RpcAccountInfoConfig { + encoding: Some(UiAccountEncoding::Base64), + data_slice: Some(UiDataSliceConfig { offset, length }), + commitment: Some(CommitmentConfig::finalized()), + min_context_slot: None, + }, + with_context: Some(false), + }; + let accounts = client + .get_program_accounts_with_config(program_id, config) + .await?; + Ok(accounts) +} + +pub fn search_and_validate_account( + accounts: Vec<(Pubkey, Account)>, + message_account: F, +) -> ChainResult +where + F: Fn(&Account) -> ChainResult, +{ + for (pubkey, account) in accounts { + let expected_pubkey = message_account(&account)?; + if expected_pubkey == pubkey { + return Ok(pubkey); + } + } + + Err(ChainCommunicationError::from_other_str( + "Could not find valid storage PDA pubkey", + )) +} diff --git a/rust/main/chains/hyperlane-sealevel/src/error.rs b/rust/main/chains/hyperlane-sealevel/src/error.rs index ff0142c393..4ba2171191 100644 --- a/rust/main/chains/hyperlane-sealevel/src/error.rs +++ b/rust/main/chains/hyperlane-sealevel/src/error.rs @@ -1,6 +1,7 @@ -use hyperlane_core::ChainCommunicationError; +use hyperlane_core::{ChainCommunicationError, H512}; use solana_client::client_error::ClientError; use solana_sdk::pubkey::ParsePubkeyError; +use solana_transaction_status::EncodedTransaction; /// Errors from the crates specific to the hyperlane-sealevel /// implementation. @@ -17,6 +18,21 @@ pub enum HyperlaneSealevelError { /// Decoding error #[error("{0}")] Decoding(#[from] solana_sdk::bs58::decode::Error), + /// No transaction in block error + #[error("{0}")] + NoTransactions(String), + /// Too many transactions of particular content in block + #[error("{0}")] + TooManyTransactions(String), + /// Unsupported transaction encoding + #[error("{0:?}")] + UnsupportedTransactionEncoding(EncodedTransaction), + /// Unsigned transaction + #[error("{0}")] + UnsignedTransaction(H512), + /// Incorrect transaction + #[error("received incorrect transaction, expected hash: {0:?}, received hash: {1:?}")] + IncorrectTransaction(Box, Box), } impl From for ChainCommunicationError { diff --git a/rust/main/chains/hyperlane-sealevel/src/interchain_gas.rs b/rust/main/chains/hyperlane-sealevel/src/interchain_gas.rs index d2f78eb4b4..3c2cbd1c16 100644 --- a/rust/main/chains/hyperlane-sealevel/src/interchain_gas.rs +++ b/rust/main/chains/hyperlane-sealevel/src/interchain_gas.rs @@ -1,25 +1,22 @@ +use std::ops::RangeInclusive; + use async_trait::async_trait; -use hyperlane_core::{ - config::StrOrIntParseError, ChainCommunicationError, ChainResult, ContractLocator, - HyperlaneChain, HyperlaneContract, HyperlaneDomain, HyperlaneProvider, Indexed, Indexer, - InterchainGasPaymaster, InterchainGasPayment, LogMeta, SequenceAwareIndexer, H256, H512, -}; +use derive_new::new; use hyperlane_sealevel_igp::{ accounts::{GasPaymentAccount, ProgramDataAccount}, igp_gas_payment_pda_seeds, igp_program_data_pda_seeds, }; -use solana_account_decoder::{UiAccountEncoding, UiDataSliceConfig}; -use solana_client::{ - rpc_config::{RpcAccountInfoConfig, RpcProgramAccountsConfig}, - rpc_filter::{Memcmp, MemcmpEncodedBytes, RpcFilterType}, -}; -use std::ops::RangeInclusive; +use solana_sdk::{account::Account, pubkey::Pubkey}; use tracing::{info, instrument}; -use crate::{ConnectionConf, SealevelProvider, SealevelRpcClient}; -use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey}; +use hyperlane_core::{ + config::StrOrIntParseError, ChainCommunicationError, ChainResult, ContractLocator, + HyperlaneChain, HyperlaneContract, HyperlaneDomain, HyperlaneProvider, Indexed, Indexer, + InterchainGasPaymaster, InterchainGasPayment, LogMeta, SequenceAwareIndexer, H256, H512, +}; -use derive_new::new; +use crate::account::{search_accounts_by_discriminator, search_and_validate_account}; +use crate::{ConnectionConf, SealevelProvider, SealevelRpcClient}; /// The offset to get the `unique_gas_payment_pubkey` field from the serialized GasPaymentData. /// The account data includes prefixes that are accounted for here: a 1 byte initialized flag @@ -121,70 +118,23 @@ impl SealevelInterchainGasPaymasterIndexer { &self, sequence_number: u64, ) -> ChainResult { - let payment_bytes = &[ - &hyperlane_sealevel_igp::accounts::GAS_PAYMENT_DISCRIMINATOR[..], - &sequence_number.to_le_bytes()[..], - ] - .concat(); - #[allow(deprecated)] - let payment_bytes: String = base64::encode(payment_bytes); - - // First, find all accounts with the matching gas payment data. - // To keep responses small in case there is ever more than 1 - // match, we don't request the full account data, and just request - // the `unique_gas_payment_pubkey` field. - #[allow(deprecated)] - let memcmp = RpcFilterType::Memcmp(Memcmp { - // Ignore the first byte, which is the `initialized` bool flag. - offset: 1, - bytes: MemcmpEncodedBytes::Base64(payment_bytes), - encoding: None, - }); - let config = RpcProgramAccountsConfig { - filters: Some(vec![memcmp]), - account_config: RpcAccountInfoConfig { - encoding: Some(UiAccountEncoding::Base64), - // Don't return any data - data_slice: Some(UiDataSliceConfig { - offset: UNIQUE_GAS_PAYMENT_PUBKEY_OFFSET, - length: 32, // the length of the `unique_gas_payment_pubkey` field - }), - commitment: Some(CommitmentConfig::finalized()), - min_context_slot: None, - }, - with_context: Some(false), - }; - tracing::debug!(config=?config, "Fetching program accounts"); - let accounts = self - .rpc_client - .get_program_accounts_with_config(&self.igp.program_id, config) - .await?; + let discriminator = hyperlane_sealevel_igp::accounts::GAS_PAYMENT_DISCRIMINATOR; + let sequence_number_bytes = sequence_number.to_le_bytes(); + let unique_gas_payment_pubkey_length = 32; // the length of the `unique_gas_payment_pubkey` field + let accounts = search_accounts_by_discriminator( + &self.rpc_client, + &self.igp.program_id, + discriminator, + &sequence_number_bytes, + UNIQUE_GAS_PAYMENT_PUBKEY_OFFSET, + unique_gas_payment_pubkey_length, + ) + .await?; tracing::debug!(accounts=?accounts, "Fetched program accounts"); - // Now loop through matching accounts and find the one with a valid account pubkey - // that proves it's an actual gas payment PDA. - let mut valid_payment_pda_pubkey = Option::::None; - - for (pubkey, account) in accounts { - let unique_gas_payment_pubkey = Pubkey::new(&account.data); - let (expected_pubkey, _bump) = Pubkey::try_find_program_address( - igp_gas_payment_pda_seeds!(unique_gas_payment_pubkey), - &self.igp.program_id, - ) - .ok_or_else(|| { - ChainCommunicationError::from_other_str( - "Could not find program address for unique_gas_payment_pubkey", - ) - })?; - if expected_pubkey == pubkey { - valid_payment_pda_pubkey = Some(pubkey); - break; - } - } - - let valid_payment_pda_pubkey = valid_payment_pda_pubkey.ok_or_else(|| { - ChainCommunicationError::from_other_str("Could not find valid gas payment PDA pubkey") + let valid_payment_pda_pubkey = search_and_validate_account(accounts, |account| { + self.interchain_payment_account(account) })?; // Now that we have the valid gas payment PDA pubkey, we can get the full account data. @@ -224,6 +174,20 @@ impl SealevelInterchainGasPaymasterIndexer { H256::from(gas_payment_account.igp.to_bytes()), )) } + + fn interchain_payment_account(&self, account: &Account) -> ChainResult { + let unique_gas_payment_pubkey = Pubkey::new(&account.data); + let (expected_pubkey, _bump) = Pubkey::try_find_program_address( + igp_gas_payment_pda_seeds!(unique_gas_payment_pubkey), + &self.igp.program_id, + ) + .ok_or_else(|| { + ChainCommunicationError::from_other_str( + "Could not find program address for unique_gas_payment_pubkey", + ) + })?; + Ok(expected_pubkey) + } } #[async_trait] diff --git a/rust/main/chains/hyperlane-sealevel/src/lib.rs b/rust/main/chains/hyperlane-sealevel/src/lib.rs index 04e2218c6b..941c64a7bd 100644 --- a/rust/main/chains/hyperlane-sealevel/src/lib.rs +++ b/rust/main/chains/hyperlane-sealevel/src/lib.rs @@ -15,6 +15,7 @@ pub use solana_sdk::signer::keypair::Keypair; pub use trait_builder::*; pub use validator_announce::*; +mod account; mod error; mod interchain_gas; mod interchain_security_module; @@ -24,4 +25,6 @@ mod multisig_ism; mod provider; mod rpc; mod trait_builder; +mod transaction; +mod utils; mod validator_announce; diff --git a/rust/main/chains/hyperlane-sealevel/src/mailbox.rs b/rust/main/chains/hyperlane-sealevel/src/mailbox.rs index 5e348d9995..45e4461279 100644 --- a/rust/main/chains/hyperlane-sealevel/src/mailbox.rs +++ b/rust/main/chains/hyperlane-sealevel/src/mailbox.rs @@ -4,22 +4,15 @@ use std::{collections::HashMap, num::NonZeroU64, ops::RangeInclusive, str::FromS use async_trait::async_trait; use borsh::{BorshDeserialize, BorshSerialize}; -use jsonrpc_core::futures_util::TryFutureExt; -use tracing::{debug, info, instrument, warn}; - -use hyperlane_core::{ - accumulator::incremental::IncrementalMerkle, 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, -}; use hyperlane_sealevel_interchain_security_module_interface::{ InterchainSecurityModuleInstruction, VerifyInstruction, }; use hyperlane_sealevel_mailbox::{ - accounts::{DispatchedMessageAccount, InboxAccount, OutboxAccount}, + accounts::{ + DispatchedMessageAccount, InboxAccount, OutboxAccount, ProcessedMessage, + 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, @@ -27,6 +20,7 @@ use hyperlane_sealevel_mailbox::{ use hyperlane_sealevel_message_recipient_interface::{ HandleInstruction, MessageRecipientInstruction, }; +use jsonrpc_core::futures_util::TryFutureExt; use serializable_account_meta::SimulationReturnData; use solana_account_decoder::{UiAccountEncoding, UiDataSliceConfig}; use solana_client::{ @@ -51,10 +45,24 @@ use solana_sdk::{ }; use solana_transaction_status::{ EncodedConfirmedBlock, EncodedTransaction, EncodedTransactionWithStatusMeta, TransactionStatus, - UiInnerInstructions, UiInstruction, UiMessage, UiParsedInstruction, UiReturnDataEncoding, - UiTransaction, UiTransactionReturnData, UiTransactionStatusMeta, + UiCompiledInstruction, UiInnerInstructions, UiInstruction, UiMessage, UiParsedInstruction, + UiReturnDataEncoding, UiTransaction, UiTransactionReturnData, UiTransactionStatusMeta, +}; +use tracing::{debug, info, instrument, warn}; + +use hyperlane_core::{ + accumulator::incremental::IncrementalMerkle, 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, }; +use crate::account::{search_accounts_by_discriminator, search_and_validate_account}; +use crate::error::HyperlaneSealevelError; +use crate::transaction::search_dispatched_message_transactions; +use crate::utils::{decode_h256, decode_h512, from_base58}; use crate::{ConnectionConf, SealevelProvider, SealevelRpcClient}; const SYSTEM_PROGRAM: &str = "11111111111111111111111111111111"; @@ -653,73 +661,26 @@ impl SealevelMailboxIndexer { self.rpc().get_block_height().await } - async fn get_message_with_nonce( + async fn get_dispatched_message_with_nonce( &self, nonce: u32, ) -> ChainResult<(Indexed, LogMeta)> { - let target_message_account_bytes = &[ - &hyperlane_sealevel_mailbox::accounts::DISPATCHED_MESSAGE_DISCRIMINATOR[..], - &nonce.to_le_bytes()[..], - ] - .concat(); - let target_message_account_bytes = base64::encode(target_message_account_bytes); - - // First, find all accounts with the matching account data. - // To keep responses small in case there is ever more than 1 - // match, we don't request the full account data, and just request - // the `unique_message_pubkey` field. - let memcmp = RpcFilterType::Memcmp(Memcmp { - // Ignore the first byte, which is the `initialized` bool flag. - offset: 1, - bytes: MemcmpEncodedBytes::Base64(target_message_account_bytes), - encoding: None, - }); - let config = RpcProgramAccountsConfig { - filters: Some(vec![memcmp]), - account_config: RpcAccountInfoConfig { - encoding: Some(UiAccountEncoding::Base64), - // Don't return any data - data_slice: Some(UiDataSliceConfig { - offset: 1 + 8 + 4 + 8, // the offset to get the `unique_message_pubkey` field - length: 32, // the length of the `unique_message_pubkey` field - }), - commitment: Some(CommitmentConfig::finalized()), - min_context_slot: None, - }, - with_context: Some(false), - }; - let accounts = self - .rpc() - .get_program_accounts_with_config(&self.mailbox.program_id, config) - .await?; - - // Now loop through matching accounts and find the one with a valid account pubkey - // that proves it's an actual message storage PDA. - let mut valid_message_storage_pda_pubkey = Option::::None; - - for (pubkey, account) in accounts { - let unique_message_pubkey = Pubkey::new(&account.data); - let (expected_pubkey, _bump) = Pubkey::try_find_program_address( - mailbox_dispatched_message_pda_seeds!(unique_message_pubkey), - &self.mailbox.program_id, - ) - .ok_or_else(|| { - ChainCommunicationError::from_other_str( - "Could not find program address for unique_message_pubkey", - ) - })?; - if expected_pubkey == pubkey { - valid_message_storage_pda_pubkey = Some(pubkey); - break; - } - } + let nonce_bytes = nonce.to_le_bytes(); + let unique_dispatched_message_pubkey_offset = 1 + 8 + 4 + 8; // the offset to get the `unique_message_pubkey` field + let unique_dispatch_message_pubkey_length = 32; // the length of the `unique_message_pubkey` field + let accounts = search_accounts_by_discriminator( + self.rpc(), + &self.program_id, + &DISPATCHED_MESSAGE_DISCRIMINATOR, + &nonce_bytes, + unique_dispatched_message_pubkey_offset, + unique_dispatch_message_pubkey_length, + ) + .await?; - let valid_message_storage_pda_pubkey = - valid_message_storage_pda_pubkey.ok_or_else(|| { - ChainCommunicationError::from_other_str( - "Could not find valid message storage PDA pubkey", - ) - })?; + let valid_message_storage_pda_pubkey = search_and_validate_account(accounts, |account| { + self.dispatched_message_account(&account) + })?; // Now that we have the valid message storage PDA pubkey, we can get the full account data. let account = self @@ -733,11 +694,99 @@ impl SealevelMailboxIndexer { let hyperlane_message = HyperlaneMessage::read_from(&mut &dispatched_message_account.encoded_message[..])?; + let block = self + .mailbox + .provider + .rpc() + .get_block(dispatched_message_account.slot) + .await?; + let block_hash = decode_h256(&block.blockhash)?; + + let transactions = + block.transactions.ok_or(HyperlaneSealevelError::NoTransactions("block which should contain message dispatch transaction does not contain any transaction".to_owned()))?; + + let transaction_hashes = search_dispatched_message_transactions( + &self.mailbox.program_id, + &valid_message_storage_pda_pubkey, + transactions, + ); + + // We expect to see that there is only one message dispatch transaction + if transaction_hashes.len() > 1 { + Err(HyperlaneSealevelError::TooManyTransactions("Block contains more than one dispatch message transaction operating on the same dispatch message store PDA".to_owned()))? + } + + let (transaction_index, transaction_hash) = transaction_hashes + .into_iter() + .next() + .ok_or(HyperlaneSealevelError::NoTransactions("block which should contain message dispatch transaction does not contain any after filtering".to_owned()))?; + Ok(( hyperlane_message.into(), LogMeta { address: self.mailbox.program_id.to_bytes().into(), block_number: dispatched_message_account.slot, + block_hash, + transaction_id: transaction_hash, + transaction_index: transaction_index as u64, + log_index: U256::from(nonce), + }, + )) + } + + fn dispatched_message_account(&self, account: &Account) -> ChainResult { + let unique_message_pubkey = Pubkey::new(&account.data); + let (expected_pubkey, _bump) = Pubkey::try_find_program_address( + mailbox_dispatched_message_pda_seeds!(unique_message_pubkey), + &self.mailbox.program_id, + ) + .ok_or_else(|| { + ChainCommunicationError::from_other_str( + "Could not find program address for unique message pubkey", + ) + })?; + Ok(expected_pubkey) + } + + async fn get_delivered_message_with_nonce( + &self, + nonce: u32, + ) -> ChainResult<(Indexed, LogMeta)> { + let nonce_bytes = nonce.to_le_bytes(); + let delivered_message_id_offset = 1 + 8 + 8; // the offset to get the `message_id` field + let delivered_message_id_length = 32; + let accounts = search_accounts_by_discriminator( + self.rpc(), + &self.program_id, + &PROCESSED_MESSAGE_DISCRIMINATOR, + &nonce_bytes, + delivered_message_id_offset, + delivered_message_id_length, + ) + .await?; + + 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) + })?; + + // Now that we have the valid delivered message storage PDA pubkey, + // we can get the full account data. + let account = self + .rpc() + .get_account_with_finalized_commitment(&valid_message_storage_pda_pubkey) + .await?; + let delivered_message_account = ProcessedMessageAccount::fetch(&mut account.data.as_ref()) + .map_err(ChainCommunicationError::from_other)? + .into_inner(); + let message_id = delivered_message_account.message_id; + + Ok(( + message_id.into(), + LogMeta { + address: self.mailbox.program_id.to_bytes().into(), + block_number: delivered_message_account.slot, // TODO: get these when building out scraper support. // It's inconvenient to get these :| block_hash: H256::zero(), @@ -747,6 +796,18 @@ impl SealevelMailboxIndexer { }, )) } + + fn delivered_message_account(&self, account: &Account) -> ChainResult { + let message_id = H256::from_slice(&account.data); + let (expected_pubkey, _bump) = Pubkey::try_find_program_address( + mailbox_processed_message_pda_seeds!(message_id), + &self.mailbox.program_id, + ) + .ok_or_else(|| { + ChainCommunicationError::from_other_str("Could not find program address for message id") + })?; + Ok(expected_pubkey) + } } #[async_trait] @@ -774,7 +835,7 @@ impl Indexer for SealevelMailboxIndexer { let message_capacity = range.end().saturating_sub(*range.start()); let mut messages = Vec::with_capacity(message_capacity as usize); for nonce in range { - messages.push(self.get_message_with_nonce(nonce).await?); + messages.push(self.get_dispatched_message_with_nonce(nonce).await?); } Ok(messages) } @@ -788,9 +849,19 @@ impl Indexer for SealevelMailboxIndexer { impl Indexer for SealevelMailboxIndexer { async fn fetch_logs_in_range( &self, - _range: RangeInclusive, + range: RangeInclusive, ) -> ChainResult, LogMeta)>> { - todo!() + info!( + ?range, + "Fetching SealevelMailboxIndexer HyperlaneMessage Delivery logs" + ); + + let message_capacity = range.end().saturating_sub(*range.start()); + let mut message_ids = Vec::with_capacity(message_capacity as usize); + for nonce in range { + message_ids.push(self.get_delivered_message_with_nonce(nonce).await?); + } + Ok(message_ids) } async fn get_finalized_block_number(&self) -> ChainResult { diff --git a/rust/main/chains/hyperlane-sealevel/src/provider.rs b/rust/main/chains/hyperlane-sealevel/src/provider.rs index 932a7191ea..a0c5a41ead 100644 --- a/rust/main/chains/hyperlane-sealevel/src/provider.rs +++ b/rust/main/chains/hyperlane-sealevel/src/provider.rs @@ -1,14 +1,17 @@ -use std::{str::FromStr, sync::Arc}; +use std::sync::Arc; use async_trait::async_trait; +use solana_sdk::signature::Signature; +use solana_transaction_status::EncodedTransaction; + use hyperlane_core::{ - BlockInfo, ChainInfo, ChainResult, HyperlaneChain, HyperlaneDomain, HyperlaneProvider, - HyperlaneProviderError, TxnInfo, H256, H512, U256, + BlockInfo, ChainCommunicationError, ChainInfo, ChainResult, HyperlaneChain, HyperlaneDomain, + HyperlaneProvider, HyperlaneProviderError, TxnInfo, TxnReceiptInfo, H256, H512, U256, }; -use solana_sdk::bs58; -use solana_sdk::pubkey::Pubkey; -use crate::{error::HyperlaneSealevelError, ConnectionConf, SealevelRpcClient}; +use crate::error::HyperlaneSealevelError; +use crate::utils::{decode_h256, decode_h512, decode_pubkey}; +use crate::{ConnectionConf, SealevelRpcClient}; /// A wrapper around a Sealevel provider to get generic blockchain information. #[derive(Debug)] @@ -50,10 +53,7 @@ impl HyperlaneProvider for SealevelProvider { async fn get_block_by_height(&self, slot: u64) -> ChainResult { let confirmed_block = self.rpc_client.get_block(slot).await?; - let hash_binary = bs58::decode(confirmed_block.blockhash) - .into_vec() - .map_err(HyperlaneSealevelError::Decoding)?; - let block_hash = H256::from_slice(&hash_binary); + let block_hash = decode_h256(&confirmed_block.blockhash)?; let block_time = confirmed_block .block_time @@ -68,8 +68,55 @@ impl HyperlaneProvider for SealevelProvider { Ok(block_info) } - async fn get_txn_by_hash(&self, _hash: &H512) -> ChainResult { - todo!() // FIXME + /// TODO This method is superfluous for Solana. + /// Since we have to request full block to find transaction hash and transaction index + /// for Solana, we have all the data about transaction mach earlier before this + /// method is invoked. + /// We can refactor abstractions so that our chain-agnostic code is more suitable + /// for all chains, not only Ethereum-like chains. + async fn get_txn_by_hash(&self, hash: &H512) -> ChainResult { + let signature = Signature::new(hash.as_bytes()); + let transaction = self.rpc_client.get_transaction(&signature).await?; + + let ui_transaction = match transaction.transaction.transaction { + EncodedTransaction::Json(t) => t, + t => Err(Into::::into( + HyperlaneSealevelError::UnsupportedTransactionEncoding(t), + ))?, + }; + + let received_signature = ui_transaction + .signatures + .first() + .ok_or(HyperlaneSealevelError::UnsignedTransaction(*hash))?; + let received_hash = decode_h512(received_signature)?; + + if &received_hash != hash { + Err(Into::::into( + HyperlaneSealevelError::IncorrectTransaction( + Box::new(*hash), + Box::new(received_hash), + ), + ))?; + } + + let receipt = TxnReceiptInfo { + gas_used: Default::default(), + cumulative_gas_used: Default::default(), + effective_gas_price: None, + }; + + Ok(TxnInfo { + hash: *hash, + gas_limit: Default::default(), + max_priority_fee_per_gas: None, + max_fee_per_gas: None, + gas_price: None, + nonce: 0, + sender: Default::default(), + recipient: None, + receipt: Some(receipt), + }) } async fn is_contract(&self, _address: &H256) -> ChainResult { @@ -78,7 +125,7 @@ impl HyperlaneProvider for SealevelProvider { } async fn get_balance(&self, address: String) -> ChainResult { - let pubkey = Pubkey::from_str(&address).map_err(Into::::into)?; + let pubkey = decode_pubkey(&address)?; self.rpc_client.get_balance(&pubkey).await } diff --git a/rust/main/chains/hyperlane-sealevel/src/rpc/client.rs b/rust/main/chains/hyperlane-sealevel/src/rpc/client.rs index 44ba2c8d59..88a474cbbf 100644 --- a/rust/main/chains/hyperlane-sealevel/src/rpc/client.rs +++ b/rust/main/chains/hyperlane-sealevel/src/rpc/client.rs @@ -1,10 +1,9 @@ use base64::Engine; use borsh::{BorshDeserialize, BorshSerialize}; -use hyperlane_core::{ChainCommunicationError, ChainResult, U256}; use serializable_account_meta::{SerializableAccountMeta, SimulationReturnData}; use solana_client::{ nonblocking::rpc_client::RpcClient, rpc_config::RpcBlockConfig, - rpc_config::RpcProgramAccountsConfig, rpc_response::Response, + rpc_config::RpcProgramAccountsConfig, rpc_config::RpcTransactionConfig, rpc_response::Response, }; use solana_sdk::{ account::Account, @@ -17,9 +16,12 @@ use solana_sdk::{ transaction::Transaction, }; use solana_transaction_status::{ - TransactionStatus, UiConfirmedBlock, UiReturnDataEncoding, UiTransactionReturnData, + EncodedConfirmedTransactionWithStatusMeta, TransactionStatus, UiConfirmedBlock, + UiReturnDataEncoding, UiTransactionReturnData, }; +use hyperlane_core::{ChainCommunicationError, ChainResult, U256}; + use crate::error::HyperlaneSealevelError; pub struct SealevelRpcClient(RpcClient); @@ -99,6 +101,17 @@ impl SealevelRpcClient { Ok(account) } + pub async fn get_balance(&self, pubkey: &Pubkey) -> ChainResult { + let balance = self + .0 + .get_balance(pubkey) + .await + .map_err(Into::::into) + .map_err(ChainCommunicationError::from)?; + + Ok(balance.into()) + } + pub async fn get_block(&self, height: u64) -> ChainResult { let config = RpcBlockConfig { commitment: Some(CommitmentConfig::finalized()), @@ -170,15 +183,19 @@ impl SealevelRpcClient { .map_err(ChainCommunicationError::from_other) } - pub async fn get_balance(&self, pubkey: &Pubkey) -> ChainResult { - let balance = self - .0 - .get_balance(pubkey) + pub async fn get_transaction( + &self, + signature: &Signature, + ) -> ChainResult { + let config = RpcTransactionConfig { + commitment: Some(CommitmentConfig::finalized()), + ..Default::default() + }; + self.0 + .get_transaction_with_config(signature, config) .await - .map_err(Into::::into) - .map_err(ChainCommunicationError::from)?; - - Ok(balance.into()) + .map_err(HyperlaneSealevelError::ClientError) + .map_err(Into::into) } pub async fn is_blockhash_valid(&self, hash: &Hash) -> ChainResult { diff --git a/rust/main/chains/hyperlane-sealevel/src/transaction.rs b/rust/main/chains/hyperlane-sealevel/src/transaction.rs new file mode 100644 index 0000000000..26a0722cf2 --- /dev/null +++ b/rust/main/chains/hyperlane-sealevel/src/transaction.rs @@ -0,0 +1,188 @@ +use std::collections::HashMap; + +use hyperlane_sealevel_mailbox::instruction::Instruction; +use solana_sdk::pubkey::Pubkey; +use solana_transaction_status::option_serializer::OptionSerializer; +use solana_transaction_status::{ + EncodedTransaction, EncodedTransactionWithStatusMeta, UiCompiledInstruction, UiInstruction, + UiMessage, UiTransaction, UiTransactionStatusMeta, +}; +use tracing::warn; + +use hyperlane_core::H512; + +use crate::utils::{decode_h512, from_base58}; + +/// This function searches for a transaction which dispatches Hyperlane message and returns +/// list of hashes of such transactions. +/// +/// This function takes the mailbox program identifier and the identifier for PDA for storing +/// a dispatched message and searches a message dispatch transaction in a list of transaction. +/// The list of transaction is usually comes from a block. The function returns list of hashes +/// of such transactions. +/// +/// The transaction will be searched with the following criteria: +/// 1. Transaction contains Mailbox program id in the list of accounts. +/// 2. Transaction contains dispatched message PDA in the list of accounts. +/// 3. Transaction is performing message dispatch (OutboxDispatch). +/// +/// * `mailbox_program_id` - Identifier of Mailbox program +/// * `message_storage_pda_pubkey` - Identifier for dispatch message store PDA +/// * `transactions` - List of transactions +pub fn search_dispatched_message_transactions( + mailbox_program_id: &Pubkey, + message_storage_pda_pubkey: &Pubkey, + transactions: Vec, +) -> Vec<(usize, H512)> { + transactions + .into_iter() + .enumerate() + .filter_map(|(index, tx)| filter_by_encoding(tx).map(|(tx, meta)| (index, tx, meta))) + .filter_map(|(index, tx, meta)| { + filter_by_validity(tx, meta) + .map(|(hash, account_keys, instructions)| (index, hash, account_keys, instructions)) + }) + .filter_map(|(index, hash, account_keys, instructions)| { + filter_not_relevant( + mailbox_program_id, + message_storage_pda_pubkey, + hash, + account_keys, + instructions, + ) + .map(|hash| (index, hash)) + }) + .collect::>() +} + +fn filter_not_relevant( + mailbox_program_id: &Pubkey, + message_storage_pda_pubkey: &Pubkey, + hash: H512, + account_keys: Vec, + instructions: Vec, +) -> Option { + let account_index_map = account_index_map(account_keys); + + let mailbox_program_id_str = mailbox_program_id.to_string(); + let mailbox_program_index = match account_index_map.get(&mailbox_program_id_str) { + Some(i) => *i as u8, + None => return None, // If account keys do not contain Mailbox program, transaction is not message dispatch. + }; + + let message_storage_pda_pubkey_str = message_storage_pda_pubkey.to_string(); + let dispatch_message_pda_account_index = + match account_index_map.get(&message_storage_pda_pubkey_str) { + Some(i) => *i as u8, + None => return None, // If account keys do not contain dispatch message store PDA account, transaction is not message dispatch. + }; + + let mailbox_program_maybe = instructions + .into_iter() + .find(|instruction| instruction.program_id_index == mailbox_program_index); + + let mailbox_program = match mailbox_program_maybe { + Some(p) => p, + None => return None, // If transaction does not contain call into Mailbox, transaction is not message dispatch. + }; + + // If Mailbox program does not operate on dispatch message store PDA account, transaction is not message dispatch. + if !mailbox_program + .accounts + .contains(&dispatch_message_pda_account_index) + { + return None; + } + + let instruction_data = match from_base58(&mailbox_program.data) { + Ok(d) => d, + Err(_) => return None, // If we cannot decode instruction data, transaction is not message dispatch. + }; + + let instruction = match Instruction::from_instruction_data(&instruction_data) { + Ok(ii) => ii, + Err(_) => return None, // If we cannot parse instruction data, transaction is not message dispatch. + }; + + // If the call into Mailbox program is not OutboxDispatch, transaction is not message dispatch. + if !matches!(instruction, Instruction::OutboxDispatch(_)) { + return None; + } + + Some(hash) +} + +fn filter_by_validity( + tx: UiTransaction, + meta: UiTransactionStatusMeta, +) -> Option<(H512, Vec, Vec)> { + let Some(transaction_hash) = tx + .signatures + .first() + .map(|signature| decode_h512(signature)) + .and_then(|r| r.ok()) + else { + warn!( + transaction = ?tx, + "transaction does not have any signatures or signatures cannot be decoded", + ); + return None; + }; + + let UiMessage::Raw(message) = tx.message else { + warn!(message = ?tx.message, "we expect messages in Raw format"); + return None; + }; + + let instructions = instructions(message.instructions, meta); + + Some((transaction_hash, message.account_keys, instructions)) +} + +fn filter_by_encoding( + tx: EncodedTransactionWithStatusMeta, +) -> Option<(UiTransaction, UiTransactionStatusMeta)> { + match (tx.transaction, tx.meta) { + // We support only transactions encoded as JSON + // We need none-empty metadata as well + (EncodedTransaction::Json(t), Some(m)) => Some((t, m)), + t => { + warn!( + ?t, + "transaction is not encoded as json or metadata is empty" + ); + None + } + } +} + +fn account_index_map(account_keys: Vec) -> HashMap { + account_keys + .into_iter() + .enumerate() + .map(|(index, key)| (key, index)) + .collect::>() +} + +/// Extract all instructions from transaction +fn instructions( + instruction: Vec, + meta: UiTransactionStatusMeta, +) -> Vec { + let inner_instructions = match meta.inner_instructions { + OptionSerializer::Some(ii) => ii + .into_iter() + .flat_map(|ii| ii.instructions) + .flat_map(|ii| match ii { + UiInstruction::Compiled(ci) => Some(ci), + _ => None, + }) + .collect::>(), + OptionSerializer::None | OptionSerializer::Skip => vec![], + }; + + [instruction, inner_instructions].concat() +} + +#[cfg(test)] +mod tests; diff --git a/rust/main/chains/hyperlane-sealevel/src/transaction/tests.rs b/rust/main/chains/hyperlane-sealevel/src/transaction/tests.rs new file mode 100644 index 0000000000..759f15de97 --- /dev/null +++ b/rust/main/chains/hyperlane-sealevel/src/transaction/tests.rs @@ -0,0 +1,329 @@ +use solana_transaction_status::EncodedTransactionWithStatusMeta; + +use crate::transaction::search_dispatched_message_transactions; +use crate::utils::decode_pubkey; + +#[test] +pub fn test_search_dispatched_message_transaction() { + // given + let mailbox_program_id = decode_pubkey("E588QtVUvresuXq2KoNEwAmoifCzYGpRBdHByN9KQMbi").unwrap(); + let dispatched_message_pda_account = + decode_pubkey("6eG8PheL41qLFFUtPjSYMtsp4aoAQsMgcsYwkGCB8kwT").unwrap(); + let transaction = serde_json::from_str::(JSON).unwrap(); + let transactions = vec![transaction]; + + // when + let transaction_hashes = search_dispatched_message_transactions( + &mailbox_program_id, + &dispatched_message_pda_account, + transactions, + ); + + // then + assert!(!transaction_hashes.is_empty()); +} + +const JSON: &str = r#" +{ + "blockTime": 1729865514, + "meta": { + "computeUnitsConsumed": 171834, + "err": null, + "fee": 3564950, + "innerInstructions": [ + { + "index": 2, + "instructions": [ + { + "accounts": [ + 8, + 7, + 6, + 0 + ], + "data": "gCzo5F74HA9Pb", + "programIdIndex": 19, + "stackHeight": 2 + }, + { + "accounts": [ + 5, + 11, + 10, + 18, + 0, + 1, + 2 + ], + "data": "2Nsbnwq8JuYnSefHfRznxFtFqdPnbeydtt5kenfF8GR1ZU2XtF8jJDo4SUc2VY52V5C25WsKsQZBLsoCVQNzefgVj2bVznkThjuZuSKXJfZN9ADggiM2soRKVsAjf3xHm3CC3w3iyvK5U9LsjmYtiDNbJCFtEPRTDxsfvMS45Bg3q6EogmBN9JiZNLP", + "programIdIndex": 17, + "stackHeight": 2 + }, + { + "accounts": [ + 0, + 5 + ], + "data": "3Bxs3zrfFUZbEPqZ", + "programIdIndex": 10, + "stackHeight": 3 + }, + { + "accounts": [ + 0, + 2 + ], + "data": "11114XfZCGKrze4PNou1GXiYCJgiBCGpHks9hxjb8tFwYMjtgVtMzvriDxwYPdRqSoqztL", + "programIdIndex": 10, + "stackHeight": 3 + }, + { + "accounts": [ + 10, + 0, + 3, + 1, + 4, + 9, + 14 + ], + "data": "5MtKiLZhPB3NhS7Gus6CenAEMS2QBtpY9QtuLeVH4CkpUN7599vsYzZXhk8Vu", + "programIdIndex": 15, + "stackHeight": 2 + }, + { + "accounts": [ + 0, + 9 + ], + "data": "3Bxs4A3YxXXYy5gj", + "programIdIndex": 10, + "stackHeight": 3 + }, + { + "accounts": [ + 0, + 4 + ], + "data": "111158VjdPaAaGVkCbPZoXJqknHXBEqoypfVjf96mwePbKxAkrKfR2gUFyN7wD8ccc9g1z", + "programIdIndex": 10, + "stackHeight": 3 + } + ] + } + ], + "loadedAddresses": { + "readonly": [], + "writable": [] + }, + "logMessages": [ + "Program ComputeBudget111111111111111111111111111111 invoke [1]", + "Program ComputeBudget111111111111111111111111111111 success", + "Program ComputeBudget111111111111111111111111111111 invoke [1]", + "Program ComputeBudget111111111111111111111111111111 success", + "Program 3EpVCPUgyjq2MfGeCttyey6bs5zya5wjYZ2BE6yDg6bm invoke [1]", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]", + "Program log: Instruction: TransferChecked", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 6200 of 983051 compute units", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success", + "Program E588QtVUvresuXq2KoNEwAmoifCzYGpRBdHByN9KQMbi invoke [2]", + "Program 11111111111111111111111111111111 invoke [3]", + "Program 11111111111111111111111111111111 success", + "Program log: Protocol fee of 0 paid from FGyh1FfooV7AtVrYjFGmjMxbELC8RMxNp4xY5WY4L4md to BvZpTuYLAR77mPhH4GtvwEWUTs53GQqkgBNuXpCePVNk", + "Program 11111111111111111111111111111111 invoke [3]", + "Program 11111111111111111111111111111111 success", + "Program log: Dispatched message to 1408864445, ID 0x09c74f3e10d98c112696b72ba1609aae47616f64f28b4cb1ad8a4a710e93ee89", + "Program E588QtVUvresuXq2KoNEwAmoifCzYGpRBdHByN9KQMbi consumed 86420 of 972001 compute units", + "Program return: E588QtVUvresuXq2KoNEwAmoifCzYGpRBdHByN9KQMbi CcdPPhDZjBEmlrcroWCarkdhb2Tyi0yxrYpKcQ6T7ok=", + "Program E588QtVUvresuXq2KoNEwAmoifCzYGpRBdHByN9KQMbi success", + "Program BhNcatUDC2D5JTyeaqrdSukiVFsEHK7e3hVmKMztwefv invoke [2]", + "Program 11111111111111111111111111111111 invoke [3]", + "Program 11111111111111111111111111111111 success", + "Program 11111111111111111111111111111111 invoke [3]", + "Program 11111111111111111111111111111111 success", + "Program log: Paid IGP JAvHW21tYXE9dtdG83DReqU2b4LUexFuCbtJT5tF8X6M for 431000 gas for message 0x09c7…ee89 to 1408864445", + "Program BhNcatUDC2D5JTyeaqrdSukiVFsEHK7e3hVmKMztwefv consumed 42792 of 882552 compute units", + "Program BhNcatUDC2D5JTyeaqrdSukiVFsEHK7e3hVmKMztwefv success", + "Program log: Warp route transfer completed to destination: 1408864445, recipient: 0xd41b…f050, remote_amount: 2206478600", + "Program 3EpVCPUgyjq2MfGeCttyey6bs5zya5wjYZ2BE6yDg6bm consumed 171534 of 999700 compute units", + "Program 3EpVCPUgyjq2MfGeCttyey6bs5zya5wjYZ2BE6yDg6bm success" + ], + "postBalances": [ + 12374928, + 0, + 2241120, + 1016160, + 1872240, + 8679120, + 2039280, + 319231603414, + 2039280, + 10172586528, + 1, + 890880, + 1141440, + 3361680, + 1830480, + 1141440, + 1, + 1141440, + 1141440, + 934087680 + ], + "postTokenBalances": [ + { + "accountIndex": 6, + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "owner": "CcquFeCYNZM48kLPyG3HWxdwgigmyxPBi6iHwve9Myhj", + "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "uiTokenAmount": { + "amount": "165697511204", + "decimals": 6, + "uiAmount": 165697.511204, + "uiAmountString": "165697.511204" + } + }, + { + "accountIndex": 8, + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "owner": "FGyh1FfooV7AtVrYjFGmjMxbELC8RMxNp4xY5WY4L4md", + "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "uiTokenAmount": { + "amount": "94", + "decimals": 6, + "uiAmount": 9.4E-5, + "uiAmountString": "0.000094" + } + } + ], + "preBalances": [ + 22211372, + 0, + 0, + 1016160, + 0, + 8679120, + 2039280, + 319231603414, + 2039280, + 10170428394, + 1, + 890880, + 1141440, + 3361680, + 1830480, + 1141440, + 1, + 1141440, + 1141440, + 934087680 + ], + "preTokenBalances": [ + { + "accountIndex": 6, + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "owner": "CcquFeCYNZM48kLPyG3HWxdwgigmyxPBi6iHwve9Myhj", + "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "uiTokenAmount": { + "amount": "163491032604", + "decimals": 6, + "uiAmount": 163491.032604, + "uiAmountString": "163491.032604" + } + }, + { + "accountIndex": 8, + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "owner": "FGyh1FfooV7AtVrYjFGmjMxbELC8RMxNp4xY5WY4L4md", + "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "uiTokenAmount": { + "amount": "2206478694", + "decimals": 6, + "uiAmount": 2206.478694, + "uiAmountString": "2206.478694" + } + } + ], + "rewards": [], + "status": { + "Ok": null + } + }, + "slot": 297626301, + "transaction": { + "message": { + "accountKeys": [ + "FGyh1FfooV7AtVrYjFGmjMxbELC8RMxNp4xY5WY4L4md", + "8DqWVhEZcg4rDYwe5UFaopmGuEajiPz9L3A1ZnytMcUm", + "6eG8PheL41qLFFUtPjSYMtsp4aoAQsMgcsYwkGCB8kwT", + "8Cv4PHJ6Cf3xY7dse7wYeZKtuQv9SAN6ujt5w22a2uho", + "9yMwrDqHsbmmvYPS9h4MLPbe2biEykcL51W7qJSDL5hF", + "BvZpTuYLAR77mPhH4GtvwEWUTs53GQqkgBNuXpCePVNk", + "CcquFeCYNZM48kLPyG3HWxdwgigmyxPBi6iHwve9Myhj", + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "FDDbaNtod9pt7pmR8qtmRZJtEj9NViDA7J6cazqUjXQj", + "JAvHW21tYXE9dtdG83DReqU2b4LUexFuCbtJT5tF8X6M", + "11111111111111111111111111111111", + "37N3sbyVAd3KvQsPw42i1LWkLahzL4ninVQ4n1NmnHjS", + "3EpVCPUgyjq2MfGeCttyey6bs5zya5wjYZ2BE6yDg6bm", + "AHX3iiEPFMyygANrp15cyUr63o9qGkwkB6ki1pgpZ7gZ", + "AkeHBbE5JkwVppujCQQ6WuxsVsJtruBAjUo6fDCFp6fF", + "BhNcatUDC2D5JTyeaqrdSukiVFsEHK7e3hVmKMztwefv", + "ComputeBudget111111111111111111111111111111", + "E588QtVUvresuXq2KoNEwAmoifCzYGpRBdHByN9KQMbi", + "noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV", + "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" + ], + "header": { + "numReadonlySignedAccounts": 1, + "numReadonlyUnsignedAccounts": 10, + "numRequiredSignatures": 2 + }, + "instructions": [ + { + "accounts": [], + "data": "FjL4FH", + "programIdIndex": 16, + "stackHeight": null + }, + { + "accounts": [], + "data": "3butUEijJrLf", + "programIdIndex": 16, + "stackHeight": null + }, + { + "accounts": [ + 10, + 18, + 13, + 17, + 5, + 11, + 0, + 1, + 2, + 15, + 3, + 4, + 14, + 9, + 19, + 7, + 8, + 6 + ], + "data": "RpjV6TtUSvt6UnMXdNo4h1Ze2VGVifo65r2jqRBUq6HJKhskSnwWybXyB4NxgfvedV9vhKdmDPg8sFT64JEZvxF8VfoGdqoAFt4WFLSB", + "programIdIndex": 12, + "stackHeight": null + } + ], + "recentBlockhash": "GHQhVUy7Eq3hcps8YoG9DCd1Tb6ccQZ9xhh81ju8ujHJ" + }, + "signatures": [ + "4nRGgV9tqCuiKUXeBzWdvdk6YC9BsGWUZurAVQLMX1NwNPpysbZNwXu97Sw4aM9REwaRmWS7gaiSKXbwtmw6oLRi", + "hXjvQbAuFH9vAxZMdGqfnSjN7t7Z7NLTzRq1SG8i6fLr9LS6XahTduPWqakiTsLDyWSofvq3MSncUAkbQLEj85f" + ] + } +} +"#; diff --git a/rust/main/chains/hyperlane-sealevel/src/utils.rs b/rust/main/chains/hyperlane-sealevel/src/utils.rs new file mode 100644 index 0000000000..56b8202c61 --- /dev/null +++ b/rust/main/chains/hyperlane-sealevel/src/utils.rs @@ -0,0 +1,33 @@ +use std::str::FromStr; + +use solana_sdk::bs58; +use solana_sdk::pubkey::Pubkey; + +use hyperlane_core::{H256, H512}; + +use crate::error::HyperlaneSealevelError; + +pub fn from_base58(base58: &str) -> Result, HyperlaneSealevelError> { + let binary = bs58::decode(base58) + .into_vec() + .map_err(HyperlaneSealevelError::Decoding)?; + Ok(binary) +} + +pub fn decode_h256(base58: &str) -> Result { + let binary = from_base58(base58)?; + let hash = H256::from_slice(&binary); + + Ok(hash) +} + +pub fn decode_h512(base58: &str) -> Result { + let binary = from_base58(base58)?; + let hash = H512::from_slice(&binary); + + Ok(hash) +} + +pub fn decode_pubkey(address: &str) -> Result { + Pubkey::from_str(address).map_err(Into::::into) +} From 1c0ef45c35e8b45d4bfe67a08a34626bb28491be Mon Sep 17 00:00:00 2001 From: Trevor Porter Date: Wed, 30 Oct 2024 18:10:29 +0000 Subject: [PATCH 51/61] feat: add new ezETH addresses to app contexts (#4789) ### Description This was missed in the recent chain addition. We should consolidate this with what exists in the registry - the duplication in infra is too much atm now that we're kicking into high gear with warp routes. Deployed the relayer with this change ### Drive-by changes ### Related issues ### Backward compatibility ### Testing --- .../warp/renzo-ezETH-addresses-v3.json | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/typescript/infra/config/environments/mainnet3/warp/renzo-ezETH-addresses-v3.json b/typescript/infra/config/environments/mainnet3/warp/renzo-ezETH-addresses-v3.json index 97fd3082bd..e5c7023b23 100644 --- a/typescript/infra/config/environments/mainnet3/warp/renzo-ezETH-addresses-v3.json +++ b/typescript/infra/config/environments/mainnet3/warp/renzo-ezETH-addresses-v3.json @@ -2,9 +2,6 @@ "arbitrum": { "xERC20": "0xB26bBfC6d1F469C821Ea25099017862e7368F4E8" }, - "optimism": { - "xERC20": "0xacEB607CdF59EB8022Cc0699eEF3eCF246d149e2" - }, "base": { "xERC20": "0x2552516453368e42705D791F674b312b8b87CD9e" }, @@ -14,18 +11,27 @@ "bsc": { "xERC20": "0xE00C6185a5c19219F1FFeD213b4406a254968c26" }, - "mode": { - "xERC20": "0xC59336D8edDa9722B4f1Ec104007191Ec16f7087" - }, - "linea": { - "xERC20": "0xC59336D8edDa9722B4f1Ec104007191Ec16f7087" - }, "ethereum": { "xERC20Lockbox": "0xC59336D8edDa9722B4f1Ec104007191Ec16f7087" }, "fraxtal": { "xERC20": "0x3aE8635A4D581d40a6Edfb3f2ED480f9532994F5" }, + "linea": { + "xERC20": "0xC59336D8edDa9722B4f1Ec104007191Ec16f7087" + }, + "mode": { + "xERC20": "0xC59336D8edDa9722B4f1Ec104007191Ec16f7087" + }, + "optimism": { + "xERC20": "0xacEB607CdF59EB8022Cc0699eEF3eCF246d149e2" + }, + "sei": { + "xERC20": "0xE5163F148C82a0818545d5D34e30BC1EDA870cB9" + }, + "taiko": { + "xERC20": "0x5eAFB1D4b5BDFaFE81715EeBcC7713e418C80E78" + }, "zircuit": { "xERC20": "0x2552516453368e42705D791F674b312b8b87CD9e" } From 469f2f34030d9539f2038df302195b6a2dbc94c6 Mon Sep 17 00:00:00 2001 From: Kunal Arora <55632507+aroralanuk@users.noreply.github.com> Date: Thu, 31 Oct 2024 06:55:59 +0530 Subject: [PATCH 52/61] fix(contracts): quote management for L2->L1 hooks (#4552) ### Description - check for sufficient fees in `AbstractMessageIdAuthHook` and refund surplus - add a child hook to OPL2ToL1Hook and ArbL2ToL1Hook to use the igp to pay for the destination gas fees ~~Note: LayerzeroL2Hook currently also refunds from msg.value, will make it into issue to be fixed later as we're using the layerzero hooks right now.~~ ### Drive-by changes - None ### Related issues - fixes https://github.com/chainlight-io/2024-08-hyperlane/issues/10 ### Backward compatibility No ### Testing Fuzz --- .changeset/cuddly-baboons-drive.md | 6 ++ solidity/contracts/hooks/ArbL2ToL1Hook.sol | 35 +++++--- solidity/contracts/hooks/OPL2ToL1Hook.sol | 35 +++++--- solidity/contracts/hooks/OPStackHook.sol | 15 ++-- solidity/contracts/hooks/PolygonPosHook.sol | 13 ++- .../hooks/aggregation/ERC5164Hook.sol | 11 ++- .../hooks/layer-zero/LayerZeroV2Hook.sol | 14 +++- .../hooks/libs/AbstractMessageIdAuthHook.sol | 30 +++++-- solidity/script/DeployArbHook.s.sol | 84 ------------------- .../hooks/layerzero/LayerZeroV2Hook.t.sol | 10 +-- solidity/test/isms/ArbL2ToL1Ism.t.sol | 20 ++++- solidity/test/isms/ERC5164ISM.t.sol | 2 + solidity/test/isms/ExternalBridgeTest.sol | 45 ++++++++-- solidity/test/isms/OPL2ToL1Ism.t.sol | 23 ++++- typescript/sdk/src/hook/EvmHookModule.ts | 4 +- typescript/sdk/src/hook/EvmHookReader.ts | 3 + typescript/sdk/src/hook/schemas.ts | 1 + .../ism/metadata/arbL2ToL1.hardhat-test.ts | 25 ++++-- 18 files changed, 222 insertions(+), 154 deletions(-) create mode 100644 .changeset/cuddly-baboons-drive.md delete mode 100644 solidity/script/DeployArbHook.s.sol diff --git a/.changeset/cuddly-baboons-drive.md b/.changeset/cuddly-baboons-drive.md new file mode 100644 index 0000000000..b5a02fb3d1 --- /dev/null +++ b/.changeset/cuddly-baboons-drive.md @@ -0,0 +1,6 @@ +--- +'@hyperlane-xyz/sdk': minor +'@hyperlane-xyz/core': minor +--- + +Checking for sufficient fees in `AbstractMessageIdAuthHook` and refund surplus diff --git a/solidity/contracts/hooks/ArbL2ToL1Hook.sol b/solidity/contracts/hooks/ArbL2ToL1Hook.sol index 488dabc1ee..76c67c0ec6 100644 --- a/solidity/contracts/hooks/ArbL2ToL1Hook.sol +++ b/solidity/contracts/hooks/ArbL2ToL1Hook.sol @@ -14,17 +14,16 @@ pragma solidity >=0.8.0; @@@@@@@@@ @@@@@@@@*/ // ============ Internal Imports ============ +import {Message} from "../libs/Message.sol"; +import {IPostDispatchHook} from "../interfaces/hooks/IPostDispatchHook.sol"; import {AbstractPostDispatchHook} from "./libs/AbstractMessageIdAuthHook.sol"; import {AbstractMessageIdAuthHook} from "./libs/AbstractMessageIdAuthHook.sol"; -import {Mailbox} from "../Mailbox.sol"; +import {AbstractMessageIdAuthorizedIsm} from "../isms/hook/AbstractMessageIdAuthorizedIsm.sol"; import {StandardHookMetadata} from "./libs/StandardHookMetadata.sol"; import {Message} from "../libs/Message.sol"; import {TypeCasts} from "../libs/TypeCasts.sol"; -import {IPostDispatchHook} from "../interfaces/hooks/IPostDispatchHook.sol"; -import {MailboxClient} from "../client/MailboxClient.sol"; // ============ External Imports ============ -import {Address} from "@openzeppelin/contracts/utils/Address.sol"; import {ArbSys} from "@arbitrum/nitro-contracts/src/precompiles/ArbSys.sol"; /** @@ -35,13 +34,14 @@ import {ArbSys} from "@arbitrum/nitro-contracts/src/precompiles/ArbSys.sol"; */ contract ArbL2ToL1Hook is AbstractMessageIdAuthHook { using StandardHookMetadata for bytes; + using Message for bytes; // ============ Constants ============ // precompile contract on L2 for sending messages to L1 ArbSys public immutable arbSys; - // Immutable quote amount - uint256 public immutable GAS_QUOTE; + // child hook to call first + IPostDispatchHook public immutable childHook; // ============ Constructor ============ @@ -50,21 +50,24 @@ contract ArbL2ToL1Hook is AbstractMessageIdAuthHook { uint32 _destinationDomain, bytes32 _ism, address _arbSys, - uint256 _gasQuote + address _childHook ) AbstractMessageIdAuthHook(_mailbox, _destinationDomain, _ism) { arbSys = ArbSys(_arbSys); - GAS_QUOTE = _gasQuote; + childHook = AbstractPostDispatchHook(_childHook); } + /// @inheritdoc IPostDispatchHook function hookType() external pure override returns (uint8) { return uint8(IPostDispatchHook.Types.ARB_L2_TO_L1); } + /// @inheritdoc AbstractPostDispatchHook function _quoteDispatch( - bytes calldata, - bytes calldata + bytes calldata metadata, + bytes calldata message ) internal view override returns (uint256) { - return GAS_QUOTE; + return + metadata.msgValue(0) + childHook.quoteDispatch(metadata, message); } // ============ Internal functions ============ @@ -72,8 +75,16 @@ contract ArbL2ToL1Hook is AbstractMessageIdAuthHook { /// @inheritdoc AbstractMessageIdAuthHook function _sendMessageId( bytes calldata metadata, - bytes memory payload + bytes calldata message ) internal override { + bytes memory payload = abi.encodeCall( + AbstractMessageIdAuthorizedIsm.verifyMessageId, + message.id() + ); + + childHook.postDispatch{ + value: childHook.quoteDispatch(metadata, message) + }(metadata, message); arbSys.sendTxToL1{value: metadata.msgValue(0)}( TypeCasts.bytes32ToAddress(ism), payload diff --git a/solidity/contracts/hooks/OPL2ToL1Hook.sol b/solidity/contracts/hooks/OPL2ToL1Hook.sol index 7d0fcf36a4..e82f4f4abf 100644 --- a/solidity/contracts/hooks/OPL2ToL1Hook.sol +++ b/solidity/contracts/hooks/OPL2ToL1Hook.sol @@ -14,10 +14,13 @@ pragma solidity >=0.8.0; @@@@@@@@@ @@@@@@@@*/ // ============ Internal Imports ============ +import {Message} from "../libs/Message.sol"; import {AbstractPostDispatchHook, AbstractMessageIdAuthHook} from "./libs/AbstractMessageIdAuthHook.sol"; +import {AbstractMessageIdAuthorizedIsm} from "../isms/hook/AbstractMessageIdAuthorizedIsm.sol"; import {StandardHookMetadata} from "./libs/StandardHookMetadata.sol"; import {TypeCasts} from "../libs/TypeCasts.sol"; import {IPostDispatchHook} from "../interfaces/hooks/IPostDispatchHook.sol"; +import {InterchainGasPaymaster} from "./igp/InterchainGasPaymaster.sol"; // ============ External Imports ============ import {ICrossDomainMessenger} from "../interfaces/optimism/ICrossDomainMessenger.sol"; @@ -30,13 +33,16 @@ import {ICrossDomainMessenger} from "../interfaces/optimism/ICrossDomainMessenge */ contract OPL2ToL1Hook is AbstractMessageIdAuthHook { using StandardHookMetadata for bytes; + using Message for bytes; // ============ Constants ============ // precompile contract on L2 for sending messages to L1 ICrossDomainMessenger public immutable l2Messenger; - // Immutable quote amount - uint32 public immutable GAS_QUOTE; + // child hook to call first + IPostDispatchHook public immutable childHook; + // Minimum gas limit that the message can be executed with - OP specific + uint32 public constant MIN_GAS_LIMIT = 300_000; // ============ Constructor ============ @@ -45,10 +51,10 @@ contract OPL2ToL1Hook is AbstractMessageIdAuthHook { uint32 _destinationDomain, bytes32 _ism, address _l2Messenger, - uint32 _gasQuote + address _childHook ) AbstractMessageIdAuthHook(_mailbox, _destinationDomain, _ism) { - GAS_QUOTE = _gasQuote; l2Messenger = ICrossDomainMessenger(_l2Messenger); + childHook = AbstractPostDispatchHook(_childHook); } /// @inheritdoc IPostDispatchHook @@ -58,10 +64,11 @@ contract OPL2ToL1Hook is AbstractMessageIdAuthHook { /// @inheritdoc AbstractPostDispatchHook function _quoteDispatch( - bytes calldata, - bytes calldata + bytes calldata metadata, + bytes calldata message ) internal view override returns (uint256) { - return GAS_QUOTE; + return + metadata.msgValue(0) + childHook.quoteDispatch(metadata, message); } // ============ Internal functions ============ @@ -69,16 +76,20 @@ contract OPL2ToL1Hook is AbstractMessageIdAuthHook { /// @inheritdoc AbstractMessageIdAuthHook function _sendMessageId( bytes calldata metadata, - bytes memory payload + bytes calldata message ) internal override { - require( - msg.value >= metadata.msgValue(0) + GAS_QUOTE, - "OPL2ToL1Hook: insufficient msg.value" + bytes memory payload = abi.encodeCall( + AbstractMessageIdAuthorizedIsm.verifyMessageId, + message.id() ); + + childHook.postDispatch{ + value: childHook.quoteDispatch(metadata, message) + }(metadata, message); l2Messenger.sendMessage{value: metadata.msgValue(0)}( TypeCasts.bytes32ToAddress(ism), payload, - GAS_QUOTE + MIN_GAS_LIMIT ); } } diff --git a/solidity/contracts/hooks/OPStackHook.sol b/solidity/contracts/hooks/OPStackHook.sol index a0a33dabcd..0ddb713d3a 100644 --- a/solidity/contracts/hooks/OPStackHook.sol +++ b/solidity/contracts/hooks/OPStackHook.sol @@ -18,6 +18,7 @@ import {AbstractMessageIdAuthHook} from "./libs/AbstractMessageIdAuthHook.sol"; import {StandardHookMetadata} from "./libs/StandardHookMetadata.sol"; import {TypeCasts} from "../libs/TypeCasts.sol"; import {Message} from "../libs/Message.sol"; +import {AbstractMessageIdAuthorizedIsm} from "../isms/hook/AbstractMessageIdAuthorizedIsm.sol"; import {IPostDispatchHook} from "../interfaces/hooks/IPostDispatchHook.sol"; // ============ External Imports ============ @@ -32,6 +33,7 @@ import {Address} from "@openzeppelin/contracts/utils/Address.sol"; */ contract OPStackHook is AbstractMessageIdAuthHook { using StandardHookMetadata for bytes; + using Message for bytes; // ============ Constants ============ @@ -60,21 +62,22 @@ contract OPStackHook is AbstractMessageIdAuthHook { // ============ Internal functions ============ function _quoteDispatch( - bytes calldata, + bytes calldata metadata, bytes calldata ) internal pure override returns (uint256) { - return 0; // gas subsidized by the L2 + return metadata.msgValue(0); // gas subsidized by the L2 } /// @inheritdoc AbstractMessageIdAuthHook function _sendMessageId( bytes calldata metadata, - bytes memory payload + bytes calldata message ) internal override { - require( - metadata.msgValue(0) < 2 ** 255, - "OPStackHook: msgValue must be less than 2 ** 255" + bytes memory payload = abi.encodeCall( + AbstractMessageIdAuthorizedIsm.verifyMessageId, + message.id() ); + l1Messenger.sendMessage{value: metadata.msgValue(0)}( TypeCasts.bytes32ToAddress(ism), payload, diff --git a/solidity/contracts/hooks/PolygonPosHook.sol b/solidity/contracts/hooks/PolygonPosHook.sol index 6831f9a332..2959adc3d2 100644 --- a/solidity/contracts/hooks/PolygonPosHook.sol +++ b/solidity/contracts/hooks/PolygonPosHook.sol @@ -19,6 +19,7 @@ import {StandardHookMetadata} from "./libs/StandardHookMetadata.sol"; import {TypeCasts} from "../libs/TypeCasts.sol"; import {Message} from "../libs/Message.sol"; import {IPostDispatchHook} from "../interfaces/hooks/IPostDispatchHook.sol"; +import {AbstractMessageIdAuthorizedIsm} from "../isms/hook/AbstractMessageIdAuthorizedIsm.sol"; // ============ External Imports ============ import {FxBaseRootTunnel} from "fx-portal/contracts/tunnel/FxBaseRootTunnel.sol"; @@ -31,6 +32,7 @@ import {Address} from "@openzeppelin/contracts/utils/Address.sol"; */ contract PolygonPosHook is AbstractMessageIdAuthHook, FxBaseRootTunnel { using StandardHookMetadata for bytes; + using Message for bytes; // ============ Constructor ============ @@ -56,22 +58,27 @@ contract PolygonPosHook is AbstractMessageIdAuthHook, FxBaseRootTunnel { // ============ Internal functions ============ function _quoteDispatch( - bytes calldata, + bytes calldata metadata, bytes calldata ) internal pure override returns (uint256) { - return 0; + return metadata.msgValue(0); } /// @inheritdoc AbstractMessageIdAuthHook function _sendMessageId( bytes calldata metadata, - bytes memory payload + bytes calldata message ) internal override { require( metadata.msgValue(0) == 0, "PolygonPosHook: does not support msgValue" ); require(msg.value == 0, "PolygonPosHook: does not support msgValue"); + + bytes memory payload = abi.encodeCall( + AbstractMessageIdAuthorizedIsm.verifyMessageId, + message.id() + ); _sendMessageToChild(payload); } diff --git a/solidity/contracts/hooks/aggregation/ERC5164Hook.sol b/solidity/contracts/hooks/aggregation/ERC5164Hook.sol index 8cfc80f06a..ece1bf80d8 100644 --- a/solidity/contracts/hooks/aggregation/ERC5164Hook.sol +++ b/solidity/contracts/hooks/aggregation/ERC5164Hook.sol @@ -15,9 +15,11 @@ pragma solidity >=0.8.0; // ============ Internal Imports ============ import {TypeCasts} from "../../libs/TypeCasts.sol"; +import {Message} from "../../libs/Message.sol"; import {IPostDispatchHook} from "../../interfaces/hooks/IPostDispatchHook.sol"; import {IMessageDispatcher} from "../../interfaces/hooks/IMessageDispatcher.sol"; import {AbstractMessageIdAuthHook} from "../libs/AbstractMessageIdAuthHook.sol"; +import {AbstractMessageIdAuthorizedIsm} from "../../isms/hook/AbstractMessageIdAuthorizedIsm.sol"; // ============ External Imports ============ import {Address} from "@openzeppelin/contracts/utils/Address.sol"; @@ -28,6 +30,8 @@ import {Address} from "@openzeppelin/contracts/utils/Address.sol"; * any of the 5164 adapters. */ contract ERC5164Hook is AbstractMessageIdAuthHook { + using Message for bytes; + IMessageDispatcher public immutable dispatcher; constructor( @@ -55,9 +59,14 @@ contract ERC5164Hook is AbstractMessageIdAuthHook { function _sendMessageId( bytes calldata, /* metadata */ - bytes memory payload + bytes calldata message ) internal override { require(msg.value == 0, "ERC5164Hook: no value allowed"); + + bytes memory payload = abi.encodeCall( + AbstractMessageIdAuthorizedIsm.verifyMessageId, + message.id() + ); dispatcher.dispatchMessage( destinationDomain, TypeCasts.bytes32ToAddress(ism), diff --git a/solidity/contracts/hooks/layer-zero/LayerZeroV2Hook.sol b/solidity/contracts/hooks/layer-zero/LayerZeroV2Hook.sol index 244fea5c76..3a0955e592 100644 --- a/solidity/contracts/hooks/layer-zero/LayerZeroV2Hook.sol +++ b/solidity/contracts/hooks/layer-zero/LayerZeroV2Hook.sol @@ -18,6 +18,7 @@ import {TypeCasts} from "../../libs/TypeCasts.sol"; import {Indexed} from "../../libs/Indexed.sol"; import {IPostDispatchHook} from "../../interfaces/hooks/IPostDispatchHook.sol"; import {AbstractMessageIdAuthHook} from "../libs/AbstractMessageIdAuthHook.sol"; +import {AbstractMessageIdAuthorizedIsm} from "../../isms/hook/AbstractMessageIdAuthorizedIsm.sol"; import {StandardHookMetadata} from "../libs/StandardHookMetadata.sol"; struct LayerZeroV2Metadata { @@ -55,8 +56,13 @@ contract LayerZeroV2Hook is AbstractMessageIdAuthHook { /// @inheritdoc AbstractMessageIdAuthHook function _sendMessageId( bytes calldata metadata, - bytes memory payload + bytes calldata message ) internal override { + bytes memory payload = abi.encodeCall( + AbstractMessageIdAuthorizedIsm.verifyMessageId, + message.id() + ); + bytes calldata lZMetadata = metadata.getCustomMetadata(); ( uint32 eid, @@ -72,7 +78,9 @@ contract LayerZeroV2Hook is AbstractMessageIdAuthHook { options, false // payInLzToken ); - lZEndpoint.send{value: msg.value}(msgParams, refundAddress); + + uint256 quote = _quoteDispatch(metadata, message); + lZEndpoint.send{value: quote}(msgParams, refundAddress); } /// @dev payInZRO is hardcoded to false because zro tokens should not be directly accepted @@ -96,7 +104,7 @@ contract LayerZeroV2Hook is AbstractMessageIdAuthHook { message.senderAddress() ); - return msgFee.nativeFee; + return metadata.msgValue(0) + msgFee.nativeFee; } /** diff --git a/solidity/contracts/hooks/libs/AbstractMessageIdAuthHook.sol b/solidity/contracts/hooks/libs/AbstractMessageIdAuthHook.sol index a9e3a41e94..05c2c1d085 100644 --- a/solidity/contracts/hooks/libs/AbstractMessageIdAuthHook.sol +++ b/solidity/contracts/hooks/libs/AbstractMessageIdAuthHook.sol @@ -22,6 +22,9 @@ import {Message} from "../../libs/Message.sol"; import {StandardHookMetadata} from "./StandardHookMetadata.sol"; import {MailboxClient} from "../../client/MailboxClient.sol"; +// ============ External Imports ============ +import {Address} from "@openzeppelin/contracts/utils/Address.sol"; + /** * @title AbstractMessageIdAuthHook * @notice Message hook to inform an Abstract Message ID ISM of messages published through @@ -31,8 +34,10 @@ abstract contract AbstractMessageIdAuthHook is AbstractPostDispatchHook, MailboxClient { + using Address for address payable; using StandardHookMetadata for bytes; using Message for bytes; + using TypeCasts for bytes32; // ============ Constants ============ @@ -68,7 +73,7 @@ abstract contract AbstractMessageIdAuthHook is function _postDispatch( bytes calldata metadata, bytes calldata message - ) internal override { + ) internal virtual override { bytes32 id = message.id(); require( _isLatestDispatched(id), @@ -82,20 +87,29 @@ abstract contract AbstractMessageIdAuthHook is metadata.msgValue(0) < 2 ** 255, "AbstractMessageIdAuthHook: msgValue must be less than 2 ** 255" ); - bytes memory payload = abi.encodeCall( - AbstractMessageIdAuthorizedIsm.verifyMessageId, - id - ); - _sendMessageId(metadata, payload); + + _sendMessageId(metadata, message); + + uint256 _overpayment = msg.value - _quoteDispatch(metadata, message); + if (_overpayment > 0) { + address _refundAddress = metadata.refundAddress( + message.sender().bytes32ToAddress() + ); + require( + _refundAddress != address(0), + "AbstractPostDispatchHook: no refund address" + ); + payable(_refundAddress).sendValue(_overpayment); + } } /** * @notice Send a message to the ISM. * @param metadata The metadata for the hook caller - * @param payload The payload for call to the ISM + * @param message The message to send to the ISM */ function _sendMessageId( bytes calldata metadata, - bytes memory payload + bytes calldata message ) internal virtual; } diff --git a/solidity/script/DeployArbHook.s.sol b/solidity/script/DeployArbHook.s.sol deleted file mode 100644 index 89f35f8d44..0000000000 --- a/solidity/script/DeployArbHook.s.sol +++ /dev/null @@ -1,84 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity >=0.8.0; - -import "forge-std/Script.sol"; - -import {Mailbox} from "../../contracts/Mailbox.sol"; -import {TypeCasts} from "../../contracts/libs/TypeCasts.sol"; -import {ArbL2ToL1Hook} from "../../contracts/hooks/ArbL2ToL1Hook.sol"; -import {ArbL2ToL1Ism} from "../../contracts/isms/hook/ArbL2ToL1Ism.sol"; -import {TestRecipient} from "../../contracts/test/TestRecipient.sol"; -import {TestIsm} from "../../contracts/test/TestIsm.sol"; - -contract DeployArbHook is Script { - uint256 deployerPrivateKey; - - ArbL2ToL1Hook hook; - ArbL2ToL1Ism ism; - - uint32 constant L1_DOMAIN = 11155111; - address constant L1_MAILBOX = 0xfFAEF09B3cd11D9b20d1a19bECca54EEC2884766; - address constant L1_BRIDGE = 0x38f918D0E9F1b721EDaA41302E399fa1B79333a9; - address constant L1_ISM = 0x096A1c034c7Ad113B6dB786b7BA852cB67025458; // placeholder - bytes32 TEST_RECIPIENT = - 0x000000000000000000000000155b1cd2f7cbc58d403b9be341fab6cd77425175; // placeholder - - address constant ARBSYS = 0x0000000000000000000000000000000000000064; - address constant L2_MAILBOX = 0x598facE78a4302f11E3de0bee1894Da0b2Cb71F8; - address constant L2_HOOK = 0xd9d99AC1C645563576b8Df22cBebFC23FB60Ec73; // placeholder - - function deployIsm() external { - deployerPrivateKey = vm.envUint("DEPLOYER_PRIVATE_KEY"); - - vm.startBroadcast(deployerPrivateKey); - - ism = new ArbL2ToL1Ism(L1_BRIDGE); - - TestRecipient testRecipient = new TestRecipient(); - testRecipient.setInterchainSecurityModule(address(ism)); - - vm.stopBroadcast(); - } - - function deployHook() external { - deployerPrivateKey = vm.envUint("DEPLOYER_PRIVATE_KEY"); - - vm.startBroadcast(deployerPrivateKey); - - hook = new ArbL2ToL1Hook( - L2_MAILBOX, - L1_DOMAIN, - TypeCasts.addressToBytes32(L1_ISM), - ARBSYS, - 200_000 // estimated gas amount used for verify - ); - - vm.stopBroadcast(); - } - - function deployTestRecipient() external { - deployerPrivateKey = vm.envUint("DEPLOYER_PRIVATE_KEY"); - - vm.startBroadcast(deployerPrivateKey); - - TestIsm noopIsm = new TestIsm(); - noopIsm.setVerify(true); - TestRecipient testRecipient = new TestRecipient(); - testRecipient.setInterchainSecurityModule(address(noopIsm)); - - console.log("TestRecipient address: %s", address(testRecipient)); - - vm.stopBroadcast(); - } - - function setAuthorizedHook() external { - deployerPrivateKey = vm.envUint("DEPLOYER_PRIVATE_KEY"); - - vm.startBroadcast(deployerPrivateKey); - - ism = ArbL2ToL1Ism(L1_ISM); - ism.setAuthorizedHook(TypeCasts.addressToBytes32(L2_HOOK)); - - vm.stopBroadcast(); - } -} diff --git a/solidity/test/hooks/layerzero/LayerZeroV2Hook.t.sol b/solidity/test/hooks/layerzero/LayerZeroV2Hook.t.sol index 94225d2876..8b5d6004c1 100644 --- a/solidity/test/hooks/layerzero/LayerZeroV2Hook.t.sol +++ b/solidity/test/hooks/layerzero/LayerZeroV2Hook.t.sol @@ -147,15 +147,7 @@ contract LayerZeroV2HookTest is Test { vm.assume(balance < nativeFee - 1); vm.deal(address(this), balance); - vm.expectRevert( - abi.encodeWithSelector( - Errors.InsufficientFee.selector, - 100, - balance, - 0, - 0 - ) - ); + vm.expectRevert(); // OutOfFunds mailbox.dispatch{value: balance}( HYPERLANE_DEST_DOMAIN, address(crossChainCounterApp).addressToBytes32(), diff --git a/solidity/test/isms/ArbL2ToL1Ism.t.sol b/solidity/test/isms/ArbL2ToL1Ism.t.sol index a251cfda4d..c44665cb5e 100644 --- a/solidity/test/isms/ArbL2ToL1Ism.t.sol +++ b/solidity/test/isms/ArbL2ToL1Ism.t.sol @@ -12,6 +12,7 @@ import {ArbL2ToL1Ism} from "../../contracts/isms/hook/ArbL2ToL1Ism.sol"; import {MockArbBridge, MockArbSys} from "../../contracts/mock/MockArbBridge.sol"; import {TestRecipient} from "../../contracts/test/TestRecipient.sol"; import {ExternalBridgeTest} from "./ExternalBridgeTest.sol"; +import {TestInterchainGasPaymaster} from "../../contracts/test/TestInterchainGasPaymaster.sol"; contract ArbL2ToL1IsmTest is ExternalBridgeTest { uint256 internal constant MOCK_LEAF_INDEX = 40160; @@ -22,10 +23,10 @@ contract ArbL2ToL1IsmTest is ExternalBridgeTest { 0x0000000000000000000000000000000000000064; MockArbBridge internal arbBridge; + TestInterchainGasPaymaster internal mockOverheadIgp; function setUp() public override { // Arbitrum bridge mock setup - GAS_QUOTE = 120_000; vm.etch(L2_ARBSYS_ADDRESS, address(new MockArbSys()).code); deployAll(); @@ -38,12 +39,13 @@ contract ArbL2ToL1IsmTest is ExternalBridgeTest { function deployHook() public { originMailbox = new TestMailbox(ORIGIN_DOMAIN); + mockOverheadIgp = new TestInterchainGasPaymaster(); hook = new ArbL2ToL1Hook( address(originMailbox), DESTINATION_DOMAIN, TypeCasts.addressToBytes32(address(ism)), L2_ARBSYS_ADDRESS, - GAS_QUOTE + address(mockOverheadIgp) ); } @@ -60,6 +62,20 @@ contract ArbL2ToL1IsmTest is ExternalBridgeTest { ism.setAuthorizedHook(TypeCasts.addressToBytes32(address(hook))); } + function test_postDispatch_childHook() public { + bytes memory encodedHookData = _encodeHookData(messageId); + originMailbox.updateLatestDispatchedId(messageId); + _expectOriginExternalBridgeCall(encodedHookData); + + bytes memory igpMetadata = StandardHookMetadata.overrideGasLimit( + 78_000 + ); + + uint256 quote = hook.quoteDispatch(igpMetadata, encodedMessage); + assertEq(quote, mockOverheadIgp.quoteGasPayment(ORIGIN_DOMAIN, 78_000)); + hook.postDispatch{value: quote}(igpMetadata, encodedMessage); + } + /* ============ helper functions ============ */ function _expectOriginExternalBridgeCall( diff --git a/solidity/test/isms/ERC5164ISM.t.sol b/solidity/test/isms/ERC5164ISM.t.sol index 8063979f49..57b467fdef 100644 --- a/solidity/test/isms/ERC5164ISM.t.sol +++ b/solidity/test/isms/ERC5164ISM.t.sol @@ -150,6 +150,8 @@ contract ERC5164IsmTest is ExternalBridgeTest { function test_verify_valueAlreadyClaimed(uint256) public override {} + function testFuzz_postDispatch_refundsExtraValue(uint256) public override {} + function test_verify_false_arbitraryCall() public override {} /* ============ helper functions ============ */ diff --git a/solidity/test/isms/ExternalBridgeTest.sol b/solidity/test/isms/ExternalBridgeTest.sol index 344e001afa..dca5a3c968 100644 --- a/solidity/test/isms/ExternalBridgeTest.sol +++ b/solidity/test/isms/ExternalBridgeTest.sol @@ -18,6 +18,7 @@ abstract contract ExternalBridgeTest is Test { uint8 internal constant HYPERLANE_VERSION = 1; uint32 internal constant ORIGIN_DOMAIN = 1; uint32 internal constant DESTINATION_DOMAIN = 2; + uint256 internal constant MSG_VALUE = 1 ether; uint256 internal constant MAX_MSG_VALUE = 2 ** 255 - 1; uint256 internal GAS_QUOTE; @@ -53,7 +54,8 @@ abstract contract ExternalBridgeTest is Test { originMailbox.updateLatestDispatchedId(messageId); _expectOriginExternalBridgeCall(encodedHookData); - hook.postDispatch{value: GAS_QUOTE}(testMetadata, encodedMessage); + uint256 quote = hook.quoteDispatch(testMetadata, encodedMessage); + hook.postDispatch{value: quote}(testMetadata, encodedMessage); } function test_postDispatch_revertWhen_chainIDNotSupported() public { @@ -89,6 +91,37 @@ abstract contract ExternalBridgeTest is Test { hook.postDispatch(excessValueMetadata, encodedMessage); } + function testFuzz_postDispatch_refundsExtraValue( + uint256 extraValue + ) public virtual { + vm.assume(extraValue < MAX_MSG_VALUE); + vm.deal(address(this), address(this).balance + extraValue); + uint256 valueBefore = address(this).balance; + + bytes memory encodedHookData = _encodeHookData(messageId); + originMailbox.updateLatestDispatchedId(messageId); + _expectOriginExternalBridgeCall(encodedHookData); + + uint256 quote = hook.quoteDispatch(testMetadata, encodedMessage); + hook.postDispatch{value: quote + extraValue}( + testMetadata, + encodedMessage + ); + + assertEq(address(this).balance, valueBefore - quote); + } + + function test_postDispatch_revertWhen_insufficientValue() public { + bytes memory encodedHookData = _encodeHookData(messageId); + originMailbox.updateLatestDispatchedId(messageId); + _expectOriginExternalBridgeCall(encodedHookData); + + uint256 quote = hook.quoteDispatch(testMetadata, encodedMessage); + + vm.expectRevert(); //arithmetic underflow + hook.postDispatch{value: quote - 1}(testMetadata, encodedMessage); + } + /* ============ ISM.verifyMessageId ============ */ function test_verifyMessageId_asyncCall() public { @@ -122,17 +155,17 @@ abstract contract ExternalBridgeTest is Test { function test_verify_msgValue_asyncCall() public virtual { bytes memory encodedHookData = _encodeHookData(messageId); - _externalBridgeDestinationCall(encodedHookData, 1 ether); + _externalBridgeDestinationCall(encodedHookData, MSG_VALUE); assertTrue(ism.verify(new bytes(0), encodedMessage)); - assertEq(address(testRecipient).balance, 1 ether); + assertEq(address(testRecipient).balance, MSG_VALUE); } function test_verify_msgValue_externalBridgeCall() public virtual { bytes memory externalCalldata = _encodeExternalDestinationBridgeCall( address(hook), address(ism), - 1 ether, + MSG_VALUE, messageId ); assertTrue(ism.verify(externalCalldata, encodedMessage)); @@ -279,6 +312,8 @@ abstract contract ExternalBridgeTest is Test { address _sender ) internal virtual returns (bytes memory) {} - // meant to mock an arbitrary successful call made by the external bridge + receive() external payable {} + + // meant to be mock an arbitrary successful call made by the external bridge function verifyMessageId(bytes32 /*messageId*/) public payable {} } diff --git a/solidity/test/isms/OPL2ToL1Ism.t.sol b/solidity/test/isms/OPL2ToL1Ism.t.sol index d829e0211e..7287ba0e11 100644 --- a/solidity/test/isms/OPL2ToL1Ism.t.sol +++ b/solidity/test/isms/OPL2ToL1Ism.t.sol @@ -14,6 +14,7 @@ import {MockOptimismMessenger, MockOptimismPortal} from "../../contracts/mock/Mo import {OPL2ToL1Hook} from "../../contracts/hooks/OPL2ToL1Hook.sol"; import {OPL2ToL1Ism} from "../../contracts/isms/hook/OPL2ToL1Ism.sol"; import {ExternalBridgeTest} from "./ExternalBridgeTest.sol"; +import {TestInterchainGasPaymaster} from "../../contracts/test/TestInterchainGasPaymaster.sol"; contract OPL2ToL1IsmTest is ExternalBridgeTest { address internal constant L2_MESSENGER_ADDRESS = @@ -21,6 +22,7 @@ contract OPL2ToL1IsmTest is ExternalBridgeTest { uint256 internal constant MOCK_NONCE = 0; + TestInterchainGasPaymaster internal mockOverheadIgp; MockOptimismPortal internal portal; MockOptimismMessenger internal l1Messenger; @@ -30,7 +32,7 @@ contract OPL2ToL1IsmTest is ExternalBridgeTest { function setUp() public override { // Optimism messenger mock setup - GAS_QUOTE = 120_000; + // GAS_QUOTE = 300_000; vm.etch( L2_MESSENGER_ADDRESS, address(new MockOptimismMessenger()).code @@ -42,12 +44,13 @@ contract OPL2ToL1IsmTest is ExternalBridgeTest { function deployHook() public { originMailbox = new TestMailbox(ORIGIN_DOMAIN); + mockOverheadIgp = new TestInterchainGasPaymaster(); hook = new OPL2ToL1Hook( address(originMailbox), DESTINATION_DOMAIN, TypeCasts.addressToBytes32(address(ism)), L2_MESSENGER_ADDRESS, - uint32(GAS_QUOTE) + address(mockOverheadIgp) ); } @@ -67,6 +70,20 @@ contract OPL2ToL1IsmTest is ExternalBridgeTest { ism.setAuthorizedHook(TypeCasts.addressToBytes32(address(hook))); } + function test_postDispatch_childHook() public { + bytes memory encodedHookData = _encodeHookData(messageId); + originMailbox.updateLatestDispatchedId(messageId); + _expectOriginExternalBridgeCall(encodedHookData); + + bytes memory igpMetadata = StandardHookMetadata.overrideGasLimit( + 78_000 + ); + + uint256 quote = hook.quoteDispatch(igpMetadata, encodedMessage); + assertEq(quote, mockOverheadIgp.quoteGasPayment(ORIGIN_DOMAIN, 78_000)); + hook.postDispatch{value: quote}(igpMetadata, encodedMessage); + } + /* ============ helper functions ============ */ function _expectOriginExternalBridgeCall( @@ -76,7 +93,7 @@ contract OPL2ToL1IsmTest is ExternalBridgeTest { L2_MESSENGER_ADDRESS, abi.encodeCall( ICrossDomainMessenger.sendMessage, - (address(ism), _encodedHookData, uint32(GAS_QUOTE)) + (address(ism), _encodedHookData, uint32(300_000)) ) ); } diff --git a/typescript/sdk/src/hook/EvmHookModule.ts b/typescript/sdk/src/hook/EvmHookModule.ts index a1b6d14120..b09099464b 100644 --- a/typescript/sdk/src/hook/EvmHookModule.ts +++ b/typescript/sdk/src/hook/EvmHookModule.ts @@ -862,6 +862,8 @@ export class EvmHookModule extends HyperlaneModule< this.multiProvider.getSignerOrProvider(config.destinationChain), ); + const childHook = await this.deploy({ config: config.childHook }); + // deploy arbL1ToL1 hook const hook = await this.deployer.deployContract( chain, @@ -871,7 +873,7 @@ export class EvmHookModule extends HyperlaneModule< this.multiProvider.getDomainId(config.destinationChain), addressToBytes32(arbL2ToL1IsmAddress), config.arbSys, - BigNumber.from(200_000), // 2x estimate of executeTransaction call overhead + childHook.address, ], ); // set authorized hook on arbL2ToL1 ism diff --git a/typescript/sdk/src/hook/EvmHookReader.ts b/typescript/sdk/src/hook/EvmHookReader.ts index 1ed1f42ab7..f84eecc8aa 100644 --- a/typescript/sdk/src/hook/EvmHookReader.ts +++ b/typescript/sdk/src/hook/EvmHookReader.ts @@ -365,11 +365,14 @@ export class EvmHookReader extends HyperlaneReader implements HookReader { const destinationChainName = this.multiProvider.getChainName(destinationDomain); + const childHookAddress = await hook.childHook(); + const childHookConfig = await this.deriveHookConfig(childHookAddress); const config: WithAddress = { address, type: HookType.ARB_L2_TO_L1, destinationChain: destinationChainName, arbSys, + childHook: childHookConfig, }; this._cache.set(address, config); diff --git a/typescript/sdk/src/hook/schemas.ts b/typescript/sdk/src/hook/schemas.ts index 1111098e8c..16bd01b27f 100644 --- a/typescript/sdk/src/hook/schemas.ts +++ b/typescript/sdk/src/hook/schemas.ts @@ -46,6 +46,7 @@ export const ArbL2ToL1HookSchema = z.object({ 'address of the bridge contract on L1, optional only needed for non @arbitrum/sdk chains', ), destinationChain: z.string(), + childHook: z.lazy((): z.ZodSchema => HookConfigSchema), }); export const IgpSchema = OwnableSchema.extend({ diff --git a/typescript/sdk/src/ism/metadata/arbL2ToL1.hardhat-test.ts b/typescript/sdk/src/ism/metadata/arbL2ToL1.hardhat-test.ts index 5195710674..f2b66d9cb9 100644 --- a/typescript/sdk/src/ism/metadata/arbL2ToL1.hardhat-test.ts +++ b/typescript/sdk/src/ism/metadata/arbL2ToL1.hardhat-test.ts @@ -41,7 +41,7 @@ import { ArbL2ToL1MetadataBuilder } from './arbL2ToL1.js'; import { MetadataContext } from './builder.js'; describe('ArbL2ToL1MetadataBuilder', () => { - const origin: ChainName = 'test1'; + const origin: ChainName = 'test4'; const destination: ChainName = 'test2'; let core: HyperlaneCore; let ismFactory: HyperlaneIsmFactory; @@ -93,14 +93,29 @@ describe('ArbL2ToL1MetadataBuilder', () => { [], ); hookConfig = { - test1: { + test4: { type: HookType.ARB_L2_TO_L1, arbSys: mockArbSys.address, destinationChain: destination, + childHook: { + type: HookType.INTERCHAIN_GAS_PAYMASTER, + beneficiary: relayer.address, + owner: relayer.address, + oracleKey: relayer.address, + overhead: { + [destination]: 200000, + }, + oracleConfig: { + [destination]: { + gasPrice: '20', + tokenExchangeRate: '10000000000', + }, + }, + }, }, }; - factoryContracts = contractsMap.test1; + factoryContracts = contractsMap.test4; proxyFactoryAddresses = Object.keys(factoryContracts).reduce((acc, key) => { acc[key] = contractsMap[origin][key as keyof ProxyFactoryFactories].address; @@ -111,11 +126,11 @@ describe('ArbL2ToL1MetadataBuilder', () => { new MockArbBridge__factory(), [], ); - hookConfig.test1.bridge = arbBridge.address; + hookConfig.test4.bridge = arbBridge.address; const hookModule = await EvmHookModule.create({ chain: origin, - config: hookConfig.test1, + config: hookConfig.test4, proxyFactoryFactories: proxyFactoryAddresses, coreAddresses: core.getAddresses(origin), multiProvider, From f26453ee55a444b4ee1a1ac948ebebc92be26ac5 Mon Sep 17 00:00:00 2001 From: Kunal Arora <55632507+aroralanuk@users.noreply.github.com> Date: Thu, 31 Oct 2024 08:44:30 +0530 Subject: [PATCH 53/61] fix(contracts): add `msg.Value` to `verifyMessageId` (#4541) ### Description - Added to the msg.value to verifyMessageId to test if the msgValue passed in is the same as the msgValue used postDispatch ### Drive-by changes - None ### Related issues - fixes https://github.com/chainlight-io/2024-08-hyperlane/issues/3 ### Backward compatibility Yes ### Testing Unit tests --- .changeset/neat-sloths-agree.md | 5 ++ solidity/contracts/hooks/ArbL2ToL1Hook.sol | 4 +- solidity/contracts/hooks/OPL2ToL1Hook.sol | 4 +- solidity/contracts/hooks/OPStackHook.sol | 4 +- solidity/contracts/hooks/PolygonPosHook.sol | 4 +- .../hooks/aggregation/ERC5164Hook.sol | 9 ++-- .../hooks/layer-zero/LayerZeroV2Hook.sol | 4 +- .../hook/AbstractMessageIdAuthorizedIsm.sol | 21 +++++--- solidity/contracts/isms/hook/ArbL2ToL1Ism.sol | 13 +++-- solidity/contracts/isms/hook/ERC5164Ism.sol | 2 +- solidity/contracts/isms/hook/OPStackIsm.sol | 2 +- .../contracts/isms/hook/PolygonPosIsm.sol | 2 +- .../isms/hook/layer-zero/LayerZeroV2Ism.sol | 21 ++++++-- solidity/contracts/libs/OPL2ToL1Metadata.sol | 10 ++-- solidity/test/isms/ArbL2ToL1Ism.t.sol | 7 +-- solidity/test/isms/ERC5164ISM.t.sol | 8 ++-- solidity/test/isms/ExternalBridgeTest.sol | 48 ++++++++++++------- solidity/test/isms/OPL2ToL1Ism.t.sol | 16 +++---- solidity/test/isms/OPStackIsm.t.sol | 20 ++++---- solidity/test/isms/PolygonPosIsm.t.sol | 26 +++++----- .../test/isms/layer-zero/LayerZeroV2Ism.t.sol | 6 +-- 21 files changed, 141 insertions(+), 95 deletions(-) create mode 100644 .changeset/neat-sloths-agree.md diff --git a/.changeset/neat-sloths-agree.md b/.changeset/neat-sloths-agree.md new file mode 100644 index 0000000000..979753ebec --- /dev/null +++ b/.changeset/neat-sloths-agree.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/core': minor +--- + +Added msg.value to preverifyMessage to commit it as part of external hook payload diff --git a/solidity/contracts/hooks/ArbL2ToL1Hook.sol b/solidity/contracts/hooks/ArbL2ToL1Hook.sol index 76c67c0ec6..9a6365b240 100644 --- a/solidity/contracts/hooks/ArbL2ToL1Hook.sol +++ b/solidity/contracts/hooks/ArbL2ToL1Hook.sol @@ -78,8 +78,8 @@ contract ArbL2ToL1Hook is AbstractMessageIdAuthHook { bytes calldata message ) internal override { bytes memory payload = abi.encodeCall( - AbstractMessageIdAuthorizedIsm.verifyMessageId, - message.id() + AbstractMessageIdAuthorizedIsm.preVerifyMessage, + (message.id(), metadata.msgValue(0)) ); childHook.postDispatch{ diff --git a/solidity/contracts/hooks/OPL2ToL1Hook.sol b/solidity/contracts/hooks/OPL2ToL1Hook.sol index e82f4f4abf..165289e57f 100644 --- a/solidity/contracts/hooks/OPL2ToL1Hook.sol +++ b/solidity/contracts/hooks/OPL2ToL1Hook.sol @@ -79,8 +79,8 @@ contract OPL2ToL1Hook is AbstractMessageIdAuthHook { bytes calldata message ) internal override { bytes memory payload = abi.encodeCall( - AbstractMessageIdAuthorizedIsm.verifyMessageId, - message.id() + AbstractMessageIdAuthorizedIsm.preVerifyMessage, + (message.id(), metadata.msgValue(0)) ); childHook.postDispatch{ diff --git a/solidity/contracts/hooks/OPStackHook.sol b/solidity/contracts/hooks/OPStackHook.sol index 0ddb713d3a..695fab9cc9 100644 --- a/solidity/contracts/hooks/OPStackHook.sol +++ b/solidity/contracts/hooks/OPStackHook.sol @@ -74,8 +74,8 @@ contract OPStackHook is AbstractMessageIdAuthHook { bytes calldata message ) internal override { bytes memory payload = abi.encodeCall( - AbstractMessageIdAuthorizedIsm.verifyMessageId, - message.id() + AbstractMessageIdAuthorizedIsm.preVerifyMessage, + (message.id(), metadata.msgValue(0)) ); l1Messenger.sendMessage{value: metadata.msgValue(0)}( diff --git a/solidity/contracts/hooks/PolygonPosHook.sol b/solidity/contracts/hooks/PolygonPosHook.sol index 2959adc3d2..e3b1c76288 100644 --- a/solidity/contracts/hooks/PolygonPosHook.sol +++ b/solidity/contracts/hooks/PolygonPosHook.sol @@ -76,8 +76,8 @@ contract PolygonPosHook is AbstractMessageIdAuthHook, FxBaseRootTunnel { require(msg.value == 0, "PolygonPosHook: does not support msgValue"); bytes memory payload = abi.encodeCall( - AbstractMessageIdAuthorizedIsm.verifyMessageId, - message.id() + AbstractMessageIdAuthorizedIsm.preVerifyMessage, + (message.id(), metadata.msgValue(0)) ); _sendMessageToChild(payload); } diff --git a/solidity/contracts/hooks/aggregation/ERC5164Hook.sol b/solidity/contracts/hooks/aggregation/ERC5164Hook.sol index ece1bf80d8..e1156679c1 100644 --- a/solidity/contracts/hooks/aggregation/ERC5164Hook.sol +++ b/solidity/contracts/hooks/aggregation/ERC5164Hook.sol @@ -16,6 +16,7 @@ pragma solidity >=0.8.0; // ============ Internal Imports ============ import {TypeCasts} from "../../libs/TypeCasts.sol"; import {Message} from "../../libs/Message.sol"; +import {StandardHookMetadata} from "../libs/StandardHookMetadata.sol"; import {IPostDispatchHook} from "../../interfaces/hooks/IPostDispatchHook.sol"; import {IMessageDispatcher} from "../../interfaces/hooks/IMessageDispatcher.sol"; import {AbstractMessageIdAuthHook} from "../libs/AbstractMessageIdAuthHook.sol"; @@ -30,6 +31,7 @@ import {Address} from "@openzeppelin/contracts/utils/Address.sol"; * any of the 5164 adapters. */ contract ERC5164Hook is AbstractMessageIdAuthHook { + using StandardHookMetadata for bytes; using Message for bytes; IMessageDispatcher public immutable dispatcher; @@ -57,15 +59,14 @@ contract ERC5164Hook is AbstractMessageIdAuthHook { } function _sendMessageId( - bytes calldata, - /* metadata */ + bytes calldata metadata, bytes calldata message ) internal override { require(msg.value == 0, "ERC5164Hook: no value allowed"); bytes memory payload = abi.encodeCall( - AbstractMessageIdAuthorizedIsm.verifyMessageId, - message.id() + AbstractMessageIdAuthorizedIsm.preVerifyMessage, + (message.id(), metadata.msgValue(0)) ); dispatcher.dispatchMessage( destinationDomain, diff --git a/solidity/contracts/hooks/layer-zero/LayerZeroV2Hook.sol b/solidity/contracts/hooks/layer-zero/LayerZeroV2Hook.sol index 3a0955e592..5e0de4a8e7 100644 --- a/solidity/contracts/hooks/layer-zero/LayerZeroV2Hook.sol +++ b/solidity/contracts/hooks/layer-zero/LayerZeroV2Hook.sol @@ -59,8 +59,8 @@ contract LayerZeroV2Hook is AbstractMessageIdAuthHook { bytes calldata message ) internal override { bytes memory payload = abi.encodeCall( - AbstractMessageIdAuthorizedIsm.verifyMessageId, - message.id() + AbstractMessageIdAuthorizedIsm.preVerifyMessage, + (message.id(), metadata.msgValue(0)) ); bytes calldata lZMetadata = metadata.getCustomMetadata(); diff --git a/solidity/contracts/isms/hook/AbstractMessageIdAuthorizedIsm.sol b/solidity/contracts/isms/hook/AbstractMessageIdAuthorizedIsm.sol index 43d8513456..51a896129b 100644 --- a/solidity/contracts/isms/hook/AbstractMessageIdAuthorizedIsm.sol +++ b/solidity/contracts/isms/hook/AbstractMessageIdAuthorizedIsm.sol @@ -53,7 +53,7 @@ abstract contract AbstractMessageIdAuthorizedIsm is // ============ Events ============ /// @notice Emitted when a message is received from the external bridge - event ReceivedMessage(bytes32 indexed messageId); + event ReceivedMessage(bytes32 indexed messageId, uint256 msgValue); // ============ Initializer ============ @@ -101,7 +101,7 @@ abstract contract AbstractMessageIdAuthorizedIsm is } /** - * @notice Check if a message is verified through verifyMessageId first. + * @notice Check if a message is verified through preVerifyMessage first. * @param message Message to check. */ function isVerified(bytes calldata message) public view returns (bool) { @@ -115,24 +115,31 @@ abstract contract AbstractMessageIdAuthorizedIsm is * @dev Only callable by the authorized hook. * @param messageId Hyperlane Id of the message. */ - function verifyMessageId(bytes32 messageId) public payable virtual { + function preVerifyMessage( + bytes32 messageId, + uint256 msgValue + ) public payable virtual { require( _isAuthorized(), "AbstractMessageIdAuthorizedIsm: sender is not the hook" ); require( - msg.value < 2 ** VERIFIED_MASK_INDEX, - "AbstractMessageIdAuthorizedIsm: msg.value must be less than 2^255" + msg.value < 2 ** VERIFIED_MASK_INDEX && msg.value == msgValue, + "AbstractMessageIdAuthorizedIsm: invalid msg.value" + ); + require( + verifiedMessages[messageId] == 0, + "AbstractMessageIdAuthorizedIsm: message already verified" ); verifiedMessages[messageId] = msg.value.setBit(VERIFIED_MASK_INDEX); - emit ReceivedMessage(messageId); + emit ReceivedMessage(messageId, msgValue); } // ============ Internal Functions ============ /** - * @notice Check if sender is authorized to message `verifyMessageId`. + * @notice Check if sender is authorized to message `preVerifyMessage`. */ function _isAuthorized() internal view virtual returns (bool); } diff --git a/solidity/contracts/isms/hook/ArbL2ToL1Ism.sol b/solidity/contracts/isms/hook/ArbL2ToL1Ism.sol index 98b5f9bd61..42593f85c6 100644 --- a/solidity/contracts/isms/hook/ArbL2ToL1Ism.sol +++ b/solidity/contracts/isms/hook/ArbL2ToL1Ism.sol @@ -44,6 +44,10 @@ contract ArbL2ToL1Ism is // arbitrum nitro contract on L1 to forward verification IOutbox public arbOutbox; + uint256 private constant DATA_LENGTH = 68; + + uint256 private constant MESSAGE_ID_END = 36; + // ============ Constructor ============ constructor(address _bridge) CrossChainEnabledArbitrumL1(_bridge) { @@ -110,13 +114,16 @@ contract ArbL2ToL1Ism is l2Sender == TypeCasts.bytes32ToAddress(authorizedHook), "ArbL2ToL1Ism: l2Sender != authorizedHook" ); - // this data is an abi encoded call of verifyMessageId(bytes32 messageId) - require(data.length == 36, "ArbL2ToL1Ism: invalid data length"); + // this data is an abi encoded call of preVerifyMessage(bytes32 messageId) + require( + data.length == DATA_LENGTH, + "ArbL2ToL1Ism: invalid data length" + ); bytes32 messageId = message.id(); bytes32 convertedBytes; assembly { // data = 0x[4 bytes function signature][32 bytes messageId] - convertedBytes := mload(add(data, 36)) + convertedBytes := mload(add(data, MESSAGE_ID_END)) } // check if the parsed message id matches the message id of the message require( diff --git a/solidity/contracts/isms/hook/ERC5164Ism.sol b/solidity/contracts/isms/hook/ERC5164Ism.sol index 0e0d788c54..d2735039c0 100644 --- a/solidity/contracts/isms/hook/ERC5164Ism.sol +++ b/solidity/contracts/isms/hook/ERC5164Ism.sol @@ -44,7 +44,7 @@ contract ERC5164Ism is AbstractMessageIdAuthorizedIsm { } /** - * @notice Check if sender is authorized to message `verifyMessageId`. + * @notice Check if sender is authorized to message `preVerifyMessage`. */ function _isAuthorized() internal view override returns (bool) { return msg.sender == executor; diff --git a/solidity/contracts/isms/hook/OPStackIsm.sol b/solidity/contracts/isms/hook/OPStackIsm.sol index 1db350fec1..504ed0d9d3 100644 --- a/solidity/contracts/isms/hook/OPStackIsm.sol +++ b/solidity/contracts/isms/hook/OPStackIsm.sol @@ -49,7 +49,7 @@ contract OPStackIsm is // ============ Internal function ============ /** - * @notice Check if sender is authorized to message `verifyMessageId`. + * @notice Check if sender is authorized to message `preVerifyMessage`. */ function _isAuthorized() internal view override returns (bool) { return diff --git a/solidity/contracts/isms/hook/PolygonPosIsm.sol b/solidity/contracts/isms/hook/PolygonPosIsm.sol index 34a40360ee..8a1471d1d1 100644 --- a/solidity/contracts/isms/hook/PolygonPosIsm.sol +++ b/solidity/contracts/isms/hook/PolygonPosIsm.sol @@ -49,7 +49,7 @@ contract PolygonPosIsm is // ============ Internal function ============ /** - * @notice Check if sender is authorized to message `verifyMessageId`. + * @notice Check if sender is authorized to message `preVerifyMessage`. */ function _isAuthorized() internal view override returns (bool) { return diff --git a/solidity/contracts/isms/hook/layer-zero/LayerZeroV2Ism.sol b/solidity/contracts/isms/hook/layer-zero/LayerZeroV2Ism.sol index 1388f32cf8..fedbffcda0 100644 --- a/solidity/contracts/isms/hook/layer-zero/LayerZeroV2Ism.sol +++ b/solidity/contracts/isms/hook/layer-zero/LayerZeroV2Ism.sol @@ -57,7 +57,7 @@ contract LayerZeroV2Ism is AbstractMessageIdAuthorizedIsm { /** * @notice Entry point for receiving msg/packet from the LayerZero endpoint. * @param _lzMessage The payload of the received message. - * @dev Authorization verification is done within verifyMessageId() -> _isAuthorized() + * @dev Authorization verification is done within preVerifyMessage() -> _isAuthorized() */ function lzReceive( Origin calldata, @@ -66,25 +66,36 @@ contract LayerZeroV2Ism is AbstractMessageIdAuthorizedIsm { address, bytes calldata ) external payable { - verifyMessageId(_messageId(_lzMessage)); + preVerifyMessage(_messageId(_lzMessage), _msgValue(_lzMessage)); } // ============ Internal function ============ /** * @notice Slices the messageId from the message delivered from LayerZeroV2Hook - * @dev message is created as abi.encodeCall(AbstractMessageIdAuthorizedIsm.verifyMessageId, id) + * @dev message is created as abi.encodeCall(AbstractMessageIdAuthorizedIsm.preVerifyMessage, id) * @dev _message will be 36 bytes (4 bytes for function selector, and 32 bytes for messageId) */ function _messageId( bytes calldata _message ) internal pure returns (bytes32) { - return bytes32(_message[FUNC_SELECTOR_OFFSET:]); + return bytes32(_message[FUNC_SELECTOR_OFFSET:ORIGIN_SENDER_OFFSET]); + } + + /** + * @notice Slices the msgValue from the message delivered from LayerZeroV2Hook + * @dev message is created as abi.encodeCall(AbstractMessageIdAuthorizedIsm.preVerifyMessage, (id,msgValue)) + * @dev _message will be 68 bytes (4 bytes for function selector, and 32 bytes for messageId, another 32 for msgValue) + */ + function _msgValue( + bytes calldata _message + ) internal pure returns (uint256) { + return uint256(bytes32(_message[ORIGIN_SENDER_OFFSET:])); } /** * @notice Validates criteria to verify a message - * @dev this is called by AbstractMessageIdAuthorizedIsm.verifyMessageId + * @dev this is called by AbstractMessageIdAuthorizedIsm.preVerifyMessage * @dev parses msg.value to get parameters from lzReceive() */ function _isAuthorized() internal view override returns (bool) { diff --git a/solidity/contracts/libs/OPL2ToL1Metadata.sol b/solidity/contracts/libs/OPL2ToL1Metadata.sol index 704ce3d4d9..f0e9338912 100644 --- a/solidity/contracts/libs/OPL2ToL1Metadata.sol +++ b/solidity/contracts/libs/OPL2ToL1Metadata.sol @@ -7,7 +7,7 @@ pragma solidity >=0.8.0; */ library OPL2ToL1Metadata { // bottom offset to the start of message id in the metadata - uint256 private constant MESSAGE_ID_OFFSET = 88; + uint256 private constant MESSAGE_ID_OFFSET = 120; // from IOptimismPortal.WithdrawalTransaction // Σ { // nonce = 32 bytes @@ -20,7 +20,7 @@ library OPL2ToL1Metadata { // LENGTH = 32 bytes // } = 252 bytes uint256 private constant FIXED_METADATA_LENGTH = 252; - // metadata here is double encoded call relayMessage(..., verifyMessageId) + // metadata here is double encoded call relayMessage(..., preVerifyMessage) // Σ { // _selector = 4 bytes // _nonce = 32 bytes @@ -31,9 +31,9 @@ library OPL2ToL1Metadata { // _data // OFFSET = 32 bytes // LENGTH = 32 bytes - // PADDING + verifyMessageId = 64 bytes - // } = 292 bytes - uint256 private constant MESSENGER_CALLDATA_LENGTH = 292; + // PADDING + preVerifyMessage = 96 bytes + // } = 324 bytes + uint256 private constant MESSENGER_CALLDATA_LENGTH = 324; /** * @notice Returns the message ID. diff --git a/solidity/test/isms/ArbL2ToL1Ism.t.sol b/solidity/test/isms/ArbL2ToL1Ism.t.sol index c44665cb5e..ec0fd93921 100644 --- a/solidity/test/isms/ArbL2ToL1Ism.t.sol +++ b/solidity/test/isms/ArbL2ToL1Ism.t.sol @@ -63,7 +63,7 @@ contract ArbL2ToL1IsmTest is ExternalBridgeTest { } function test_postDispatch_childHook() public { - bytes memory encodedHookData = _encodeHookData(messageId); + bytes memory encodedHookData = _encodeHookData(messageId, 0); originMailbox.updateLatestDispatchedId(messageId); _expectOriginExternalBridgeCall(encodedHookData); @@ -131,10 +131,7 @@ contract ArbL2ToL1IsmTest is ExternalBridgeTest { bytes32 _messageId, uint256 _value ) internal view returns (bytes memory) { - bytes memory encodedHookData = abi.encodeCall( - AbstractMessageIdAuthorizedIsm.verifyMessageId, - (_messageId) - ); + bytes memory encodedHookData = _encodeHookData(_messageId, _value); bytes32[] memory proof = new bytes32[](16); return diff --git a/solidity/test/isms/ERC5164ISM.t.sol b/solidity/test/isms/ERC5164ISM.t.sol index 57b467fdef..f3900e621d 100644 --- a/solidity/test/isms/ERC5164ISM.t.sol +++ b/solidity/test/isms/ERC5164ISM.t.sol @@ -134,7 +134,7 @@ contract ERC5164IsmTest is ExternalBridgeTest { vm.expectRevert( "AbstractMessageIdAuthorizedIsm: sender is not the hook" ); - ism.verifyMessageId(messageId); + ism.preVerifyMessage(messageId, 0); assertFalse(ism.isVerified(encodedMessage)); } @@ -150,6 +150,8 @@ contract ERC5164IsmTest is ExternalBridgeTest { function test_verify_valueAlreadyClaimed(uint256) public override {} + function test_verify_override_msgValue() public override {} + function testFuzz_postDispatch_refundsExtraValue(uint256) public override {} function test_verify_false_arbitraryCall() public override {} @@ -161,7 +163,7 @@ contract ERC5164IsmTest is ExternalBridgeTest { uint256 _msgValue ) internal override { vm.prank(address(executor)); - ism.verifyMessageId(messageId); + ism.preVerifyMessage(messageId, 0); } function _encodeExternalDestinationBridgeCall( @@ -172,7 +174,7 @@ contract ERC5164IsmTest is ExternalBridgeTest { ) internal override returns (bytes memory) { if (_from == address(hook)) { vm.prank(address(executor)); - ism.verifyMessageId{value: _msgValue}(messageId); + ism.preVerifyMessage{value: _msgValue}(messageId, 0); } } } diff --git a/solidity/test/isms/ExternalBridgeTest.sol b/solidity/test/isms/ExternalBridgeTest.sol index dca5a3c968..937e39311a 100644 --- a/solidity/test/isms/ExternalBridgeTest.sol +++ b/solidity/test/isms/ExternalBridgeTest.sol @@ -50,7 +50,8 @@ abstract contract ExternalBridgeTest is Test { /* ============ Hook.postDispatch ============ */ function test_postDispatch() public { - bytes memory encodedHookData = _encodeHookData(messageId); + bytes memory hookMetadata = testMetadata; + bytes memory encodedHookData = _encodeHookData(messageId, 0); originMailbox.updateLatestDispatchedId(messageId); _expectOriginExternalBridgeCall(encodedHookData); @@ -98,7 +99,7 @@ abstract contract ExternalBridgeTest is Test { vm.deal(address(this), address(this).balance + extraValue); uint256 valueBefore = address(this).balance; - bytes memory encodedHookData = _encodeHookData(messageId); + bytes memory encodedHookData = _encodeHookData(messageId, 0); originMailbox.updateLatestDispatchedId(messageId); _expectOriginExternalBridgeCall(encodedHookData); @@ -112,7 +113,7 @@ abstract contract ExternalBridgeTest is Test { } function test_postDispatch_revertWhen_insufficientValue() public { - bytes memory encodedHookData = _encodeHookData(messageId); + bytes memory encodedHookData = _encodeHookData(messageId, 0); originMailbox.updateLatestDispatchedId(messageId); _expectOriginExternalBridgeCall(encodedHookData); @@ -122,19 +123,16 @@ abstract contract ExternalBridgeTest is Test { hook.postDispatch{value: quote - 1}(testMetadata, encodedMessage); } - /* ============ ISM.verifyMessageId ============ */ + /* ============ ISM.preVerifyMessage ============ */ - function test_verifyMessageId_asyncCall() public { - bytes memory encodedHookData = abi.encodeCall( - AbstractMessageIdAuthorizedIsm.verifyMessageId, - (messageId) - ); + function test_preVerifyMessage_asyncCall() public { + bytes memory encodedHookData = _encodeHookData(messageId, 0); _externalBridgeDestinationCall(encodedHookData, 0); assertTrue(ism.isVerified(encodedMessage)); } - function test_verifyMessageId_externalBridgeCall() public virtual { + function test_preVerifyMessage_externalBridgeCall() public virtual { bytes memory externalCalldata = _encodeExternalDestinationBridgeCall( address(hook), address(ism), @@ -154,7 +152,7 @@ abstract contract ExternalBridgeTest is Test { } function test_verify_msgValue_asyncCall() public virtual { - bytes memory encodedHookData = _encodeHookData(messageId); + bytes memory encodedHookData = _encodeHookData(messageId, MSG_VALUE); _externalBridgeDestinationCall(encodedHookData, MSG_VALUE); assertTrue(ism.verify(new bytes(0), encodedMessage)); @@ -222,7 +220,7 @@ abstract contract ExternalBridgeTest is Test { // async call - native bridges might have try catch block to prevent revert try this.externalBridgeDestinationCallWrapper( - _encodeHookData(incorrectMessageId), + _encodeHookData(incorrectMessageId, 0), 0 ) {} catch {} @@ -232,7 +230,10 @@ abstract contract ExternalBridgeTest is Test { /// forge-config: default.fuzz.runs = 10 function test_verify_valueAlreadyClaimed(uint256 _msgValue) public virtual { _msgValue = bound(_msgValue, 0, MAX_MSG_VALUE); - _externalBridgeDestinationCall(_encodeHookData(messageId), _msgValue); + _externalBridgeDestinationCall( + _encodeHookData(messageId, _msgValue), + _msgValue + ); bool verified = ism.verify(new bytes(0), encodedMessage); assertTrue(verified); @@ -250,6 +251,18 @@ abstract contract ExternalBridgeTest is Test { assertEq(address(testRecipient).balance, _msgValue); } + function test_verify_override_msgValue() public virtual { + bytes memory encodedHookData = _encodeHookData(messageId, MSG_VALUE); + + _externalBridgeDestinationCall(encodedHookData, MSG_VALUE); + + vm.expectRevert("AbstractMessageIdAuthorizedIsm: invalid msg.value"); + _externalBridgeDestinationCall(encodedHookData, 0); + + assertTrue(ism.verify(new bytes(0), encodedMessage)); + assertEq(address(testRecipient).balance, MSG_VALUE); + } + function test_verify_false_arbitraryCall() public virtual { bytes memory incorrectCalldata = _encodeExternalDestinationBridgeCall( address(hook), @@ -275,12 +288,13 @@ abstract contract ExternalBridgeTest is Test { } function _encodeHookData( - bytes32 _messageId + bytes32 _messageId, + uint256 _msgValue ) internal pure returns (bytes memory) { return abi.encodeCall( - AbstractMessageIdAuthorizedIsm.verifyMessageId, - (_messageId) + AbstractMessageIdAuthorizedIsm.preVerifyMessage, + (_messageId, _msgValue) ); } @@ -315,5 +329,5 @@ abstract contract ExternalBridgeTest is Test { receive() external payable {} // meant to be mock an arbitrary successful call made by the external bridge - function verifyMessageId(bytes32 /*messageId*/) public payable {} + function preVerifyMessage(bytes32 /*messageId*/) public payable {} } diff --git a/solidity/test/isms/OPL2ToL1Ism.t.sol b/solidity/test/isms/OPL2ToL1Ism.t.sol index 7287ba0e11..9322713d5e 100644 --- a/solidity/test/isms/OPL2ToL1Ism.t.sol +++ b/solidity/test/isms/OPL2ToL1Ism.t.sol @@ -71,7 +71,7 @@ contract OPL2ToL1IsmTest is ExternalBridgeTest { } function test_postDispatch_childHook() public { - bytes memory encodedHookData = _encodeHookData(messageId); + bytes memory encodedHookData = _encodeHookData(messageId, 0); originMailbox.updateLatestDispatchedId(messageId); _expectOriginExternalBridgeCall(encodedHookData); @@ -117,8 +117,7 @@ contract OPL2ToL1IsmTest is ExternalBridgeTest { } function _externalBridgeDestinationCall( - bytes memory, - /*_encodedHookData*/ + bytes memory _encodedHookData, uint256 _msgValue ) internal override { vm.deal(address(portal), _msgValue); @@ -132,7 +131,7 @@ contract OPL2ToL1IsmTest is ExternalBridgeTest { data: _encodeMessengerCalldata( address(ism), _msgValue, - messageId + _encodedHookData ) }); portal.finalizeWithdrawalTransaction(withdrawal); @@ -141,10 +140,8 @@ contract OPL2ToL1IsmTest is ExternalBridgeTest { function _encodeMessengerCalldata( address _ism, uint256 _value, - bytes32 _messageId + bytes memory _encodedHookData ) internal view returns (bytes memory) { - bytes memory encodedHookData = _encodeHookData(_messageId); - return abi.encodeCall( ICrossDomainMessenger.relayMessage, @@ -154,7 +151,7 @@ contract OPL2ToL1IsmTest is ExternalBridgeTest { _ism, _value, uint256(GAS_QUOTE), - encodedHookData + _encodedHookData ) ); } @@ -164,6 +161,7 @@ contract OPL2ToL1IsmTest is ExternalBridgeTest { uint256 _value, bytes32 _messageId ) internal view returns (bytes memory) { + bytes memory encodedHookData = _encodeHookData(_messageId, _value); return abi.encode( MOCK_NONCE, @@ -171,7 +169,7 @@ contract OPL2ToL1IsmTest is ExternalBridgeTest { l1Messenger, _value, uint256(GAS_QUOTE), - _encodeMessengerCalldata(_ism, _value, _messageId) + _encodeMessengerCalldata(_ism, _value, encodedHookData) ); } } diff --git a/solidity/test/isms/OPStackIsm.t.sol b/solidity/test/isms/OPStackIsm.t.sol index 3230e59b82..8371c3b681 100644 --- a/solidity/test/isms/OPStackIsm.t.sol +++ b/solidity/test/isms/OPStackIsm.t.sol @@ -99,7 +99,10 @@ contract OPStackIsmTest is ExternalBridgeTest { function test_verify_revertsWhen_incorrectMessageId() public override { bytes32 incorrectMessageId = keccak256("incorrect message id"); - _externalBridgeDestinationCall(_encodeHookData(incorrectMessageId), 0); + _externalBridgeDestinationCall( + _encodeHookData(incorrectMessageId, 0), + 0 + ); assertFalse(ism.isVerified(testMessage)); } @@ -142,7 +145,7 @@ contract OPStackIsmTest is ExternalBridgeTest { } // SKIP - no external bridge call - function test_verifyMessageId_externalBridgeCall() public override {} + function test_preVerifyMessage_externalBridgeCall() public override {} function test_verify_msgValue_externalBridgeCall() public override {} @@ -150,12 +153,12 @@ contract OPStackIsmTest is ExternalBridgeTest { function test_verify_false_arbitraryCall() public override {} - /* ============ ISM.verifyMessageId ============ */ + /* ============ ISM.preVerifyMessage ============ */ function test_verify_revertsWhen_notAuthorizedHook() public override { // needs to be called by the canonical messenger on Optimism vm.expectRevert(NotCrossChainCall.selector); - ism.verifyMessageId(messageId); + ism.preVerifyMessage(messageId, 0); vm.startPrank(L2_MESSENGER_ADDRESS); _setExternalOriginSender(address(this)); @@ -164,7 +167,7 @@ contract OPStackIsmTest is ExternalBridgeTest { vm.expectRevert( "AbstractMessageIdAuthorizedIsm: sender is not the hook" ); - ism.verifyMessageId(messageId); + ism.preVerifyMessage(messageId, 0); } function _setExternalOriginSender( @@ -179,10 +182,11 @@ contract OPStackIsmTest is ExternalBridgeTest { function test_verify_tooMuchValue() public { uint256 _msgValue = 2 ** 255 + 1; - vm.expectRevert( - "AbstractMessageIdAuthorizedIsm: msg.value must be less than 2^255" + vm.expectRevert("AbstractMessageIdAuthorizedIsm: invalid msg.value"); + _externalBridgeDestinationCall( + _encodeHookData(messageId, _msgValue), + _msgValue ); - _externalBridgeDestinationCall(_encodeHookData(messageId), _msgValue); assertFalse(ism.isVerified(encodedMessage)); diff --git a/solidity/test/isms/PolygonPosIsm.t.sol b/solidity/test/isms/PolygonPosIsm.t.sol index fd26afea29..7d30dca4fd 100644 --- a/solidity/test/isms/PolygonPosIsm.t.sol +++ b/solidity/test/isms/PolygonPosIsm.t.sol @@ -78,7 +78,7 @@ contract PolygonPosIsmTest is Test { bytes data ); - event ReceivedMessage(bytes32 indexed messageId); + event ReceivedMessage(bytes32 indexed messageId, uint256 msgValue); function setUp() public { // block numbers to fork from, chain data is cached to ../../forge-cache/ @@ -155,8 +155,8 @@ contract PolygonPosIsmTest is Test { vm.selectFork(mainnetFork); bytes memory encodedHookData = abi.encodeCall( - AbstractMessageIdAuthorizedIsm.verifyMessageId, - (messageId) + AbstractMessageIdAuthorizedIsm.preVerifyMessage, + (messageId, 0) ); l1Mailbox.updateLatestDispatchedId(messageId); @@ -228,22 +228,22 @@ contract PolygonPosIsmTest is Test { polygonPosHook.postDispatch(testMetadata, encodedMessage); } - /* ============ ISM.verifyMessageId ============ */ + /* ============ ISM.preVerifyMessage ============ */ - function testFork_verifyMessageId() public { + function testFork_preVerifyMessage() public { deployAll(); vm.selectFork(polygonPosFork); bytes memory encodedHookData = abi.encodeCall( - AbstractMessageIdAuthorizedIsm.verifyMessageId, - (messageId) + AbstractMessageIdAuthorizedIsm.preVerifyMessage, + (messageId, 0) ); vm.startPrank(POLYGON_CROSSCHAIN_SYSTEM_ADDR); vm.expectEmit(true, false, false, false, address(polygonPosISM)); - emit ReceivedMessage(messageId); + emit ReceivedMessage(messageId, 0); // FIX: expect other events fxChild.onStateReceive( @@ -259,14 +259,14 @@ contract PolygonPosIsmTest is Test { vm.stopPrank(); } - function testFork_verifyMessageId_RevertWhen_NotAuthorized() public { + function testFork_preVerifyMessage_RevertWhen_NotAuthorized() public { deployAll(); vm.selectFork(polygonPosFork); // needs to be called by the fxchild on Polygon vm.expectRevert(NotCrossChainCall.selector); - polygonPosISM.verifyMessageId(messageId); + polygonPosISM.preVerifyMessage(messageId, 0); vm.startPrank(MAINNET_FX_CHILD); @@ -274,7 +274,7 @@ contract PolygonPosIsmTest is Test { vm.expectRevert( "AbstractMessageIdAuthorizedIsm: sender is not the hook" ); - polygonPosISM.verifyMessageId(messageId); + polygonPosISM.preVerifyMessage(messageId, 0); } /* ============ ISM.verify ============ */ @@ -349,8 +349,8 @@ contract PolygonPosIsmTest is Test { vm.selectFork(polygonPosFork); bytes memory encodedHookData = abi.encodeCall( - AbstractMessageIdAuthorizedIsm.verifyMessageId, - (_messageId) + AbstractMessageIdAuthorizedIsm.preVerifyMessage, + (_messageId, 0) ); vm.prank(POLYGON_CROSSCHAIN_SYSTEM_ADDR); diff --git a/solidity/test/isms/layer-zero/LayerZeroV2Ism.t.sol b/solidity/test/isms/layer-zero/LayerZeroV2Ism.t.sol index 0d778a4d92..ac3ff38cfb 100644 --- a/solidity/test/isms/layer-zero/LayerZeroV2Ism.t.sol +++ b/solidity/test/isms/layer-zero/LayerZeroV2Ism.t.sol @@ -23,8 +23,8 @@ contract LayerZeroV2IsmTest is Test { ) internal pure returns (bytes memory) { return abi.encodeCall( - AbstractMessageIdAuthorizedIsm.verifyMessageId, - (_messageId) + AbstractMessageIdAuthorizedIsm.preVerifyMessage, + (_messageId, 0) ); } @@ -133,7 +133,7 @@ contract LayerZeroV2IsmTest is Test { vm.stopPrank(); } - function testLzV2Ism_verifyMessageId_SetsCorrectMessageId( + function testLzV2Ism_preVerifyMessage_SetsCorrectMessageId( bytes32 messageId ) public { lZIsm.setAuthorizedHook(hook.addressToBytes32()); From 21a0cf964fe34000443a427c2db92caceb969d32 Mon Sep 17 00:00:00 2001 From: IvanPsurtcev <87025698+IvanPsurtcev@users.noreply.github.com> Date: Thu, 31 Oct 2024 15:26:39 +0300 Subject: [PATCH 54/61] fix: GCS folder support (#4652) ### Description This PR fixes the problem of this issue: [https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/4449](url) The change was only in the gcs_storage.rs file. Slightly tweaked methods so that you can write files to a specified folder in the terminal. When trying to run a validator specifying checkpointSyncer settings for GCS: `--checkpointSyncer.type gcs --checkpointSyncer.bucket an-s3-signatures-repository --checkpointSyncer.folder atletaolympia` ``` Building provider without signer thread 'main' panicked at 'Failed to report agent metadata: error validating bucket name an-s3-signatures-repository/atletaolympia Caused by: Character '/' @ 27 is not allowed Location: hyperlane-base/src/types/gcs_storage.rs:181:9', agents/validator/src/validator.rs:178:14 ``` To solve this problem I added the `folder: Option` field to the GcsStorageClient structure, I also added bucket and folder processing to the impl CheckpointSyncer, GcsStorageClientBuilder methods --- .../hyperlane-base/src/types/gcs_storage.rs | 154 +++++++++++++----- 1 file changed, 109 insertions(+), 45 deletions(-) diff --git a/rust/main/hyperlane-base/src/types/gcs_storage.rs b/rust/main/hyperlane-base/src/types/gcs_storage.rs index ba40ec2ca9..a413edb323 100644 --- a/rust/main/hyperlane-base/src/types/gcs_storage.rs +++ b/rust/main/hyperlane-base/src/types/gcs_storage.rs @@ -4,6 +4,7 @@ use derive_new::new; use eyre::{bail, Result}; use hyperlane_core::{ReorgEvent, SignedAnnouncement, SignedCheckpointWithMessageId}; use std::fmt; +use tracing::{error, info, instrument}; use ya_gcp::{ storage::{ api::{error::HttpStatusError, http::StatusCode, Error}, @@ -16,6 +17,7 @@ const LATEST_INDEX_KEY: &str = "gcsLatestIndexKey"; const METADATA_KEY: &str = "gcsMetadataKey"; const ANNOUNCEMENT_KEY: &str = "gcsAnnouncementKey"; const REORG_FLAG_KEY: &str = "gcsReorgFlagKey"; + /// Path to GCS users_secret file pub const GCS_USER_SECRET: &str = "GCS_USER_SECRET"; /// Path to GCS Service account key @@ -80,12 +82,14 @@ pub struct GcsStorageClient { inner: StorageClient, // bucket name of this client's storage bucket: String, + // folder name of this client's storage + folder: Option, } impl GcsStorageClientBuilder { /// Instantiates `ya_gcp:StorageClient` based on provided auth method /// # Param - /// * `baucket_name` - String name of target bucket to work with, will be used by all store and get ops + /// * `bucket_name` - String name of target bucket to work with, will be used by all store and get ops pub async fn build( self, bucket_name: impl Into, @@ -94,21 +98,71 @@ impl GcsStorageClientBuilder { let inner = ClientBuilder::new(ClientBuilderConfig::new().auth_flow(self.auth)) .await? .build_storage_client(); - let bucket = if let Some(folder) = folder { - format! {"{}/{}", bucket_name.into(), folder} - } else { - bucket_name.into() - }; - Ok(GcsStorageClient { inner, bucket }) + let bucket = bucket_name.into(); + let mut processed_folder = folder; + + if let Some(ref mut folder_str) = processed_folder { + if folder_str.ends_with('/') { + folder_str.truncate(folder_str.trim_end_matches('/').len()); + info!( + "Trimmed trailing '/' from folder name. New folder: '{}'", + folder_str + ); + } + } + + GcsStorageClient::validate_bucket_name(&bucket)?; + Ok(GcsStorageClient { + inner, + bucket, + folder: processed_folder, + }) } } impl GcsStorageClient { - // convenience formatter + // Convenience formatter fn get_checkpoint_key(index: u32) -> String { format!("checkpoint_{index}_with_id.json") } + + fn object_path(&self, object_name: &str) -> String { + if let Some(folder) = &self.folder { + format!("{}/{}", folder, object_name) + } else { + object_name.to_string() + } + } + + fn validate_bucket_name(bucket: &str) -> Result<()> { + if bucket.contains('/') { + error!("Bucket name '{}' has an invalid symbol '/'", bucket); + bail!("Bucket name '{}' has an invalid symbol '/'", bucket) + } else { + Ok(()) + } + } + + /// Uploads data to GCS and logs the result. + #[instrument(skip(self, data))] + async fn upload_and_log(&self, object_name: &str, data: Vec) -> Result<()> { + match self + .inner + .insert_object(&self.bucket, object_name, data) + .await + { + Ok(_) => { + info!("Successfully uploaded to '{}'", object_name); + Ok(()) + } + Err(e) => { + error!("Failed to upload to '{}': {:?}", object_name, e); + Err(e.into()) + } + } + } + // #test only method[s] #[cfg(test)] pub(crate) async fn get_by_path(&self, path: impl AsRef) -> Result<()> { @@ -117,11 +171,12 @@ impl GcsStorageClient { } } -// required by `CheckpointSyncer` +// Required by `CheckpointSyncer` impl fmt::Debug for GcsStorageClient { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("S3Storage") + f.debug_struct("GcsStorageClient") .field("bucket", &self.bucket) + .field("folder", &self.folder) .finish() } } @@ -129,6 +184,7 @@ impl fmt::Debug for GcsStorageClient { #[async_trait] impl CheckpointSyncer for GcsStorageClient { /// Read the highest index of this Syncer + #[instrument(skip(self))] async fn latest_index(&self) -> Result> { match self.inner.get_object(&self.bucket, LATEST_INDEX_KEY).await { Ok(data) => Ok(Some(serde_json::from_slice(data.as_ref())?)), @@ -144,15 +200,14 @@ impl CheckpointSyncer for GcsStorageClient { } /// Writes the highest index of this Syncer + #[instrument(skip(self, index))] async fn write_latest_index(&self, index: u32) -> Result<()> { - let d = serde_json::to_vec(&index)?; - self.inner - .insert_object(&self.bucket, LATEST_INDEX_KEY, d) - .await?; - Ok(()) + let data = serde_json::to_vec(&index)?; + self.upload_and_log(LATEST_INDEX_KEY, data).await } /// Update the latest index of this syncer if necessary + #[instrument(skip(self, index))] async fn update_latest_index(&self, index: u32) -> Result<()> { let curr = self.latest_index().await?.unwrap_or(0); if index > curr { @@ -162,6 +217,7 @@ impl CheckpointSyncer for GcsStorageClient { } /// Attempt to fetch the signed (checkpoint, messageId) tuple at this index + #[instrument(skip(self, index))] async fn fetch_checkpoint(&self, index: u32) -> Result> { match self .inner @@ -179,56 +235,64 @@ impl CheckpointSyncer for GcsStorageClient { } /// Write the signed (checkpoint, messageId) tuple to this syncer + #[instrument(skip(self, signed_checkpoint))] async fn write_checkpoint( &self, signed_checkpoint: &SignedCheckpointWithMessageId, ) -> Result<()> { - self.inner - .insert_object( - &self.bucket, - GcsStorageClient::get_checkpoint_key(signed_checkpoint.value.index), - serde_json::to_vec(signed_checkpoint)?, - ) - .await?; - Ok(()) + let object_name = Self::get_checkpoint_key(signed_checkpoint.value.index); + let data = serde_json::to_vec(signed_checkpoint)?; + self.upload_and_log(&object_name, data).await } /// Write the agent metadata to this syncer + #[instrument(skip(self, metadata))] async fn write_metadata(&self, metadata: &AgentMetadata) -> Result<()> { - let serialized_metadata = serde_json::to_string_pretty(metadata)?; - self.inner - .insert_object(&self.bucket, METADATA_KEY, serialized_metadata) - .await?; - Ok(()) + let object_name = self.object_path(METADATA_KEY); + let data = serde_json::to_string_pretty(metadata)?.into_bytes(); + self.upload_and_log(&object_name, data).await } /// Write the signed announcement to this syncer - async fn write_announcement(&self, signed_announcement: &SignedAnnouncement) -> Result<()> { - self.inner - .insert_object( - &self.bucket, - ANNOUNCEMENT_KEY, - serde_json::to_string(signed_announcement)?, - ) - .await?; - Ok(()) + #[instrument(skip(self, announcement))] + async fn write_announcement(&self, announcement: &SignedAnnouncement) -> Result<()> { + let object_name = self.object_path(ANNOUNCEMENT_KEY); + let data = serde_json::to_string(announcement)?.into_bytes(); + self.upload_and_log(&object_name, data).await } /// Return the announcement storage location for this syncer + #[instrument(skip(self))] fn announcement_location(&self) -> String { - format!("gs://{}/{}", &self.bucket, ANNOUNCEMENT_KEY) + let location = format!( + "gs://{}/{}", + &self.bucket, + self.object_path(ANNOUNCEMENT_KEY) + ); + info!("Announcement storage location: '{}'", location); + location } - async fn write_reorg_status(&self, reorged_event: &ReorgEvent) -> Result<()> { - let serialized_metadata = serde_json::to_string_pretty(reorged_event)?; - self.inner - .insert_object(&self.bucket, REORG_FLAG_KEY, serialized_metadata) - .await?; - Ok(()) + /// Write the reorg status to this syncer + #[instrument(skip(self, reorg_event))] + async fn write_reorg_status(&self, reorg_event: &ReorgEvent) -> Result<()> { + let object_name = REORG_FLAG_KEY; + let data = serde_json::to_string_pretty(reorg_event)?.into_bytes(); + self.upload_and_log(object_name, data).await } + /// Read the reorg status from this syncer + #[instrument(skip(self))] async fn reorg_status(&self) -> Result> { - Ok(None) + match self.inner.get_object(&self.bucket, REORG_FLAG_KEY).await { + Ok(data) => Ok(Some(serde_json::from_slice(data.as_ref())?)), + Err(e) => match e { + ObjectError::Failure(Error::HttpStatus(HttpStatusError(StatusCode::NOT_FOUND))) => { + Ok(None) + } + _ => bail!(e), + }, + } } } From 38bd1ae49f884358ee022b3488a25663cba80870 Mon Sep 17 00:00:00 2001 From: Danil Nemirovsky Date: Thu, 31 Oct 2024 12:41:30 +0000 Subject: [PATCH 55/61] feat: Scraper stores input data from Ethereum transactions (#4794) ### Description Scraper stores input data from Ethereum transactions ### Related issues - Fixes https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/4778 ### Backward compatibility Yes ### Testing Manual run of Scraper against 1. local database 2. database restored from backup of production database Co-authored-by: Danil Nemirovsky <4614623+ameten@users.noreply.github.com> --- .../agents/scraper/migration/bin/generate_entities.rs | 4 ++-- .../src/m20230309_000003_create_table_transaction.rs | 9 +++++++++ rust/main/agents/scraper/src/db/generated/transaction.rs | 3 +++ rust/main/agents/scraper/src/db/txn.rs | 1 + .../hyperlane-cosmos/src/providers/cosmos/provider.rs | 1 + .../hyperlane-ethereum/src/rpc_clients/provider.rs | 7 +++++-- rust/main/chains/hyperlane-fuel/src/provider.rs | 1 + rust/main/chains/hyperlane-sealevel/src/provider.rs | 1 + rust/main/hyperlane-core/src/types/chain_data.rs | 2 ++ 9 files changed, 25 insertions(+), 4 deletions(-) diff --git a/rust/main/agents/scraper/migration/bin/generate_entities.rs b/rust/main/agents/scraper/migration/bin/generate_entities.rs index 5ec3e6c66a..9481ece179 100644 --- a/rust/main/agents/scraper/migration/bin/generate_entities.rs +++ b/rust/main/agents/scraper/migration/bin/generate_entities.rs @@ -57,8 +57,8 @@ impl Drop for PostgresDockerContainer { async fn main() -> Result<(), DbErr> { assert_eq!( std::env::current_dir().unwrap().file_name().unwrap(), - "rust", - "Must run from the rust dir" + "main", + "Must run from the rust/main dir" ); let postgres = PostgresDockerContainer::start(); diff --git a/rust/main/agents/scraper/migration/src/m20230309_000003_create_table_transaction.rs b/rust/main/agents/scraper/migration/src/m20230309_000003_create_table_transaction.rs index daacfb6b7a..283968c6d6 100644 --- a/rust/main/agents/scraper/migration/src/m20230309_000003_create_table_transaction.rs +++ b/rust/main/agents/scraper/migration/src/m20230309_000003_create_table_transaction.rs @@ -52,6 +52,13 @@ impl MigrationTrait for Migration { .col(ColumnDef::new_with_type(Transaction::Recipient, Address).borrow_mut()) .col(ColumnDef::new_with_type(Transaction::GasUsed, Wei).not_null()) .col(ColumnDef::new_with_type(Transaction::CumulativeGasUsed, Wei).not_null()) + .col( + ColumnDef::new_with_type( + Transaction::RawInputData, + ColumnType::Binary(BlobSize::Blob(None)), + ) + .borrow_mut(), + ) .foreign_key( ForeignKey::create() .from_col(Transaction::BlockId) @@ -128,4 +135,6 @@ pub enum Transaction { GasUsed, /// Cumulative gas used within the block after this was executed CumulativeGasUsed, + /// Raw input data from Ethereum transaction + RawInputData, } diff --git a/rust/main/agents/scraper/src/db/generated/transaction.rs b/rust/main/agents/scraper/src/db/generated/transaction.rs index 216545b372..4f1139c8c9 100644 --- a/rust/main/agents/scraper/src/db/generated/transaction.rs +++ b/rust/main/agents/scraper/src/db/generated/transaction.rs @@ -27,6 +27,7 @@ pub struct Model { pub recipient: Option>, pub gas_used: BigDecimal, pub cumulative_gas_used: BigDecimal, + pub raw_input_data: Option>, } #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] @@ -45,6 +46,7 @@ pub enum Column { Recipient, GasUsed, CumulativeGasUsed, + RawInputData, } #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] @@ -85,6 +87,7 @@ impl ColumnTrait for Column { Self::Recipient => ColumnType::Binary(BlobSize::Blob(None)).def().null(), Self::GasUsed => ColumnType::Decimal(Some((78u32, 0u32))).def(), Self::CumulativeGasUsed => ColumnType::Decimal(Some((78u32, 0u32))).def(), + Self::RawInputData => ColumnType::Binary(BlobSize::Blob(None)).def().null(), } } } diff --git a/rust/main/agents/scraper/src/db/txn.rs b/rust/main/agents/scraper/src/db/txn.rs index d5cec03dc7..c482569cb7 100644 --- a/rust/main/agents/scraper/src/db/txn.rs +++ b/rust/main/agents/scraper/src/db/txn.rs @@ -94,6 +94,7 @@ impl ScraperDb { recipient: Set(txn.recipient.as_ref().map(address_to_bytes)), max_fee_per_gas: Set(txn.max_fee_per_gas.map(u256_to_decimal)), cumulative_gas_used: Set(u256_to_decimal(receipt.cumulative_gas_used)), + raw_input_data: Set(txn.raw_input_data.clone()), }) }) .collect::>>()?; diff --git a/rust/main/chains/hyperlane-cosmos/src/providers/cosmos/provider.rs b/rust/main/chains/hyperlane-cosmos/src/providers/cosmos/provider.rs index 2ab0388be7..62076974bc 100644 --- a/rust/main/chains/hyperlane-cosmos/src/providers/cosmos/provider.rs +++ b/rust/main/chains/hyperlane-cosmos/src/providers/cosmos/provider.rs @@ -441,6 +441,7 @@ impl HyperlaneProvider for CosmosProvider { cumulative_gas_used: U256::from(response.tx_result.gas_used), effective_gas_price: Some(gas_price), }), + raw_input_data: None, }; Ok(tx_info) diff --git a/rust/main/chains/hyperlane-ethereum/src/rpc_clients/provider.rs b/rust/main/chains/hyperlane-ethereum/src/rpc_clients/provider.rs index d89cf9f43f..44a9f99e73 100644 --- a/rust/main/chains/hyperlane-ethereum/src/rpc_clients/provider.rs +++ b/rust/main/chains/hyperlane-ethereum/src/rpc_clients/provider.rs @@ -106,7 +106,7 @@ where }) .transpose()?; - Ok(TxnInfo { + let txn_info = TxnInfo { hash: *hash, max_fee_per_gas: txn.max_fee_per_gas.map(Into::into), max_priority_fee_per_gas: txn.max_priority_fee_per_gas.map(Into::into), @@ -116,7 +116,10 @@ where sender: txn.from.into(), recipient: txn.to.map(Into::into), receipt, - }) + raw_input_data: Some(txn.input.to_vec()), + }; + + Ok(txn_info) } #[instrument(err, skip(self))] diff --git a/rust/main/chains/hyperlane-fuel/src/provider.rs b/rust/main/chains/hyperlane-fuel/src/provider.rs index cdd32650e1..f4c6ee1345 100644 --- a/rust/main/chains/hyperlane-fuel/src/provider.rs +++ b/rust/main/chains/hyperlane-fuel/src/provider.rs @@ -381,6 +381,7 @@ impl HyperlaneProvider for FuelProvider { gas_price: Some(gas_price.into()), recipient, receipt: None, + raw_input_data: None, }) } None => Err(ChainCommunicationError::CustomError(format!( diff --git a/rust/main/chains/hyperlane-sealevel/src/provider.rs b/rust/main/chains/hyperlane-sealevel/src/provider.rs index a0c5a41ead..1b03872ed6 100644 --- a/rust/main/chains/hyperlane-sealevel/src/provider.rs +++ b/rust/main/chains/hyperlane-sealevel/src/provider.rs @@ -116,6 +116,7 @@ impl HyperlaneProvider for SealevelProvider { sender: Default::default(), recipient: None, receipt: Some(receipt), + raw_input_data: None, }) } diff --git a/rust/main/hyperlane-core/src/types/chain_data.rs b/rust/main/hyperlane-core/src/types/chain_data.rs index 219f6989da..b4c80c8461 100644 --- a/rust/main/hyperlane-core/src/types/chain_data.rs +++ b/rust/main/hyperlane-core/src/types/chain_data.rs @@ -49,6 +49,8 @@ pub struct TxnInfo { /// If the txn has been processed, we can also report some additional /// information. pub receipt: Option, + /// Raw input data of a transaction + pub raw_input_data: Option>, } /// Information about the execution of a transaction. From fa066909142099d61d526956e95814fbc891a4e2 Mon Sep 17 00:00:00 2001 From: Danil Nemirovsky Date: Thu, 31 Oct 2024 16:09:35 +0000 Subject: [PATCH 56/61] feat: Upgrade Scraper so that it stores input data for Ethereum transactions (#4796) ### Description Upgrade Scraper so that it stores input data for Ethereum transactions ### Related issues - Fixes https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/4778 ### Backward compatibility Yes ### Testing Manual run of Scraper with local database Manual run of Scraper with db restored from production backup Co-authored-by: Danil Nemirovsky <4614623+ameten@users.noreply.github.com> --- typescript/infra/config/environments/mainnet3/agent.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typescript/infra/config/environments/mainnet3/agent.ts b/typescript/infra/config/environments/mainnet3/agent.ts index aa79f5e432..456e6605c9 100644 --- a/typescript/infra/config/environments/mainnet3/agent.ts +++ b/typescript/infra/config/environments/mainnet3/agent.ts @@ -484,7 +484,7 @@ const hyperlane: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: '45399a3-20241025-210128', + tag: '38bd1ae-20241031-125333', }, resources: scraperResources, }, From ede43a71fd1535f7007d59ee94312d0fc6d202c7 Mon Sep 17 00:00:00 2001 From: -f Date: Mon, 4 Nov 2024 16:45:59 +0530 Subject: [PATCH 57/61] hypValue --- .../hooks/libs/StandardHookMetadata.sol | 13 +++ solidity/contracts/token/HypValue.sol | 102 ++++++++++++++++++ solidity/test/token/HypValue.t.sol | 11 ++ 3 files changed, 126 insertions(+) create mode 100644 solidity/contracts/token/HypValue.sol create mode 100644 solidity/test/token/HypValue.t.sol diff --git a/solidity/contracts/hooks/libs/StandardHookMetadata.sol b/solidity/contracts/hooks/libs/StandardHookMetadata.sol index e12f6822ad..108491b3d8 100644 --- a/solidity/contracts/hooks/libs/StandardHookMetadata.sol +++ b/solidity/contracts/hooks/libs/StandardHookMetadata.sol @@ -165,4 +165,17 @@ library StandardHookMetadata { ) internal pure returns (bytes memory) { return formatMetadata(uint256(0), uint256(0), _refundAddress, ""); } + + function overrideMsgValue( + bytes calldata _metadata, + uint256 _msgValue + ) internal view returns (bytes memory) { + return + formatMetadata( + _msgValue, + gasLimit(_metadata, 0), + refundAddress(_metadata, msg.sender), + getCustomMetadata(_metadata) + ); + } } diff --git a/solidity/contracts/token/HypValue.sol b/solidity/contracts/token/HypValue.sol new file mode 100644 index 0000000000..49c8d4254d --- /dev/null +++ b/solidity/contracts/token/HypValue.sol @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity >=0.8.0; + +import {TokenRouter} from "./libs/TokenRouter.sol"; +import {TokenMessage} from "./libs/TokenMessage.sol"; +import {StandardHookMetadata} from "../hooks/libs/StandardHookMetadata.sol"; + +// Note: this assumes 1:1 exchange rate between source and destination chain +contract HypValue is TokenRouter { + error InsufficientValue(uint256 amount, uint256 value); + + constructor(address _mailbox) TokenRouter(_mailbox) {} + + function initialize( + address _valuehook, + address _interchainSecurityModule, + address _owner + ) public initializer { + _MailboxClient_initialize( + _valuehook, + _interchainSecurityModule, + _owner + ); + } + + function transferRemote( + uint32 _destination, + bytes32 _recipient, + uint256 _amount, + bytes calldata _hookMetadata, + address _hook + ) public payable virtual override returns (bytes32 messageId) { + _checkSufficientValue(_destination, _amount); + + bytes memory hookMetadata = StandardHookMetadata.overrideMsgValue( + _hookMetadata, + _amount + ); + + return + _transferRemote( + _destination, + _recipient, + _amount, + _amount, + hookMetadata, + _hook + ); + } + + function transferRemote( + uint32 _destination, + bytes32 _recipient, + uint256 _amount + ) external payable virtual override returns (bytes32 messageId) { + _checkSufficientValue(_destination, _amount); + bytes memory hookMetadata = StandardHookMetadata.formatMetadata( + _amount, + destinationGas[_destination], + msg.sender, + "" + ); + + return + _transferRemote( + _destination, + _recipient, + _amount, + _amount, + hookMetadata, + address(hook) + ); + } + + function _transferFromSender( + uint256 + ) internal pure override returns (bytes memory) { + return bytes(""); // no metadata + } + + function _transferTo( + address _recipient, + uint256 _amount, + bytes calldata // no metadata + ) internal virtual override {} + + function balanceOf( + address /* _account */ + ) external pure override returns (uint256) { + return 0; + } + + function _checkSufficientValue( + uint32 _destination, + uint256 _amount + ) internal view { + uint256 quote = this.quoteGasPayment(_destination); + if (msg.value < _amount + quote) { + revert InsufficientValue(_amount + quote, msg.value); + } + } +} diff --git a/solidity/test/token/HypValue.t.sol b/solidity/test/token/HypValue.t.sol new file mode 100644 index 0000000000..b2cb28b844 --- /dev/null +++ b/solidity/test/token/HypValue.t.sol @@ -0,0 +1,11 @@ +import {HypTokenTest} from "./HypERC20.t.sol"; + +// abstract contract OlympixUnitTest is HypTokenTest { +// constructor(string memory name) {} +// } + +// contract HypValueTest is OlympixUnitTest("HypValue") { +// function setUp() public override { +// super.setUp(); +// } +// } From b5762d5d246dd2bc777bee7152a96103be4b0ff2 Mon Sep 17 00:00:00 2001 From: -f Date: Mon, 4 Nov 2024 19:30:03 +0530 Subject: [PATCH 58/61] test --- solidity/contracts/token/HypValue.sol | 4 +- solidity/test/token/HypERC20.t.sol | 4 +- solidity/test/token/HypValue.t.sol | 83 ++++++++++++++++++++++++--- 3 files changed, 79 insertions(+), 12 deletions(-) diff --git a/solidity/contracts/token/HypValue.sol b/solidity/contracts/token/HypValue.sol index 49c8d4254d..bd0bc02d6d 100644 --- a/solidity/contracts/token/HypValue.sol +++ b/solidity/contracts/token/HypValue.sol @@ -28,7 +28,7 @@ contract HypValue is TokenRouter { bytes32 _recipient, uint256 _amount, bytes calldata _hookMetadata, - address _hook + address /*_hook*/ ) public payable virtual override returns (bytes32 messageId) { _checkSufficientValue(_destination, _amount); @@ -44,7 +44,7 @@ contract HypValue is TokenRouter { _amount, _amount, hookMetadata, - _hook + address(hook) ); } diff --git a/solidity/test/token/HypERC20.t.sol b/solidity/test/token/HypERC20.t.sol index c3b7da74cd..964a5346f9 100644 --- a/solidity/test/token/HypERC20.t.sol +++ b/solidity/test/token/HypERC20.t.sol @@ -352,7 +352,7 @@ contract HypERC20Test is HypTokenTest { assertEq(erc20Token.balanceOf(BOB), 100e18); } - function testRemoteTransfer() public { + function testRemoteTransfer() public virtual { remoteToken.enrollRemoteRouter( ORIGIN, address(localToken).addressToBytes32() @@ -413,7 +413,7 @@ contract HypERC20CollateralTest is HypTokenTest { function testInitialize_revert_ifAlreadyInitialized() public {} - function testRemoteTransfer() public { + function testRemoteTransfer() public virtual { uint256 balanceBefore = localToken.balanceOf(ALICE); vm.prank(ALICE); diff --git a/solidity/test/token/HypValue.t.sol b/solidity/test/token/HypValue.t.sol index b2cb28b844..6be1501163 100644 --- a/solidity/test/token/HypValue.t.sol +++ b/solidity/test/token/HypValue.t.sol @@ -1,11 +1,78 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity >=0.8.0; + +import {TypeCasts} from "../../contracts/libs/TypeCasts.sol"; import {HypTokenTest} from "./HypERC20.t.sol"; +import {HypValue} from "../../contracts/token/HypValue.sol"; +import {OPL2ToL1Hook} from "../../contracts/hooks/OPL2ToL1Hook.sol"; +import {OPL2ToL1Ism} from "../../contracts/isms/hook/OPL2ToL1Ism.sol"; +import {MockOptimismMessenger, MockOptimismPortal} from "../../contracts/mock/MockOptimism.sol"; +import {TestInterchainGasPaymaster} from "../../contracts/test/TestInterchainGasPaymaster.sol"; + +contract HypValueTest is HypTokenTest { + using TypeCasts for address; + + address internal constant L2_MESSENGER_ADDRESS = + 0x4200000000000000000000000000000000000007; + + HypValue internal valueRouter; + OPL2ToL1Hook internal valueHook; + OPL2ToL1Ism internal ism; + TestInterchainGasPaymaster internal mockOverheadIgp; + MockOptimismPortal internal portal; + MockOptimismMessenger internal l1Messenger; + + function setUp() public override { + super.setUp(); + vm.etch( + L2_MESSENGER_ADDRESS, + address(new MockOptimismMessenger()).code + ); + + localToken = new HypValue(address(localMailbox)); + valueRouter = HypValue(payable(address(localToken))); + + l1Messenger = new MockOptimismMessenger(); + portal = new MockOptimismPortal(); + l1Messenger.setPORTAL(address(portal)); + ism = new OPL2ToL1Ism(address(l1Messenger)); + + mockOverheadIgp = new TestInterchainGasPaymaster(); + valueHook = new OPL2ToL1Hook( + address(localMailbox), + DESTINATION, + address(localMailbox).addressToBytes32(), + L2_MESSENGER_ADDRESS, + address(mockOverheadIgp) + ); + + valueRouter.initialize(address(valueHook), address(ism), address(this)); + + valueRouter.enrollRemoteRouter( + DESTINATION, + address(remoteToken).addressToBytes32() + ); + remoteToken.enrollRemoteRouter( + ORIGIN, + address(localToken).addressToBytes32() + ); + + vm.deal(ALICE, 1000e18); + } + + function testRemoteTransfer() public { + // uint256 balanceBefore = localToken.balanceOf(ALICE); + + uint256 quote = valueRouter.quoteGasPayment(DESTINATION); + uint256 msgValue = TRANSFER_AMT + quote; -// abstract contract OlympixUnitTest is HypTokenTest { -// constructor(string memory name) {} -// } + // vm.prank(ALICE); + _performRemoteTransferWithEmit(msgValue, TRANSFER_AMT, quote); + // assertEq(localToken.balanceOf(ALICE), balanceBefore - TRANSFER_AMT); + } -// contract HypValueTest is OlympixUnitTest("HypValue") { -// function setUp() public override { -// super.setUp(); -// } -// } + function testTransfer_withHookSpecified( + uint256 fee, + bytes calldata metadata + ) public override {} +} From 6616b9871c8b5bccead8871b41d23ce19ce35c2b Mon Sep 17 00:00:00 2001 From: -f Date: Tue, 5 Nov 2024 14:40:26 +0530 Subject: [PATCH 59/61] test transfer --- solidity/contracts/hooks/OPL2ToL1Hook.sol | 9 +- .../hooks/libs/StandardHookMetadata.sol | 13 +++ solidity/contracts/token/HypValue.sol | 23 +++-- solidity/test/token/HypValue.t.sol | 99 ++++++++++++++++--- 4 files changed, 121 insertions(+), 23 deletions(-) diff --git a/solidity/contracts/hooks/OPL2ToL1Hook.sol b/solidity/contracts/hooks/OPL2ToL1Hook.sol index 165289e57f..cca5219194 100644 --- a/solidity/contracts/hooks/OPL2ToL1Hook.sol +++ b/solidity/contracts/hooks/OPL2ToL1Hook.sol @@ -67,8 +67,10 @@ contract OPL2ToL1Hook is AbstractMessageIdAuthHook { bytes calldata metadata, bytes calldata message ) internal view override returns (uint256) { + bytes memory metadataWithGasLimit = metadata.overrideGasLimit(78_000); return - metadata.msgValue(0) + childHook.quoteDispatch(metadata, message); + metadata.msgValue(0) + + childHook.quoteDispatch(metadataWithGasLimit, message); } // ============ Internal functions ============ @@ -83,9 +85,10 @@ contract OPL2ToL1Hook is AbstractMessageIdAuthHook { (message.id(), metadata.msgValue(0)) ); + bytes memory metadataWithGasLimit = metadata.overrideGasLimit(78_000); childHook.postDispatch{ - value: childHook.quoteDispatch(metadata, message) - }(metadata, message); + value: childHook.quoteDispatch(metadataWithGasLimit, message) + }(metadataWithGasLimit, message); l2Messenger.sendMessage{value: metadata.msgValue(0)}( TypeCasts.bytes32ToAddress(ism), payload, diff --git a/solidity/contracts/hooks/libs/StandardHookMetadata.sol b/solidity/contracts/hooks/libs/StandardHookMetadata.sol index 108491b3d8..165ee6820d 100644 --- a/solidity/contracts/hooks/libs/StandardHookMetadata.sol +++ b/solidity/contracts/hooks/libs/StandardHookMetadata.sol @@ -178,4 +178,17 @@ library StandardHookMetadata { getCustomMetadata(_metadata) ); } + + function overrideGasLimit( + bytes calldata _metadata, + uint256 _gasLimit + ) internal view returns (bytes memory) { + return + formatMetadata( + msgValue(_metadata, 0), + _gasLimit, + refundAddress(_metadata, msg.sender), + getCustomMetadata(_metadata) + ); + } } diff --git a/solidity/contracts/token/HypValue.sol b/solidity/contracts/token/HypValue.sol index bd0bc02d6d..30d04386e7 100644 --- a/solidity/contracts/token/HypValue.sol +++ b/solidity/contracts/token/HypValue.sol @@ -4,6 +4,7 @@ pragma solidity >=0.8.0; import {TokenRouter} from "./libs/TokenRouter.sol"; import {TokenMessage} from "./libs/TokenMessage.sol"; import {StandardHookMetadata} from "../hooks/libs/StandardHookMetadata.sol"; +import {Address} from "@openzeppelin/contracts/utils/Address.sol"; // Note: this assumes 1:1 exchange rate between source and destination chain contract HypValue is TokenRouter { @@ -23,14 +24,15 @@ contract HypValue is TokenRouter { ); } + // use _hook with caution function transferRemote( uint32 _destination, bytes32 _recipient, uint256 _amount, bytes calldata _hookMetadata, - address /*_hook*/ + address _hook ) public payable virtual override returns (bytes32 messageId) { - _checkSufficientValue(_destination, _amount); + uint256 quote = _checkSufficientValue(_destination, _amount); bytes memory hookMetadata = StandardHookMetadata.overrideMsgValue( _hookMetadata, @@ -42,9 +44,9 @@ contract HypValue is TokenRouter { _destination, _recipient, _amount, - _amount, + _amount + quote, hookMetadata, - address(hook) + _hook ); } @@ -53,7 +55,7 @@ contract HypValue is TokenRouter { bytes32 _recipient, uint256 _amount ) external payable virtual override returns (bytes32 messageId) { - _checkSufficientValue(_destination, _amount); + uint256 quote = _checkSufficientValue(_destination, _amount); bytes memory hookMetadata = StandardHookMetadata.formatMetadata( _amount, destinationGas[_destination], @@ -66,7 +68,7 @@ contract HypValue is TokenRouter { _destination, _recipient, _amount, - _amount, + _amount + quote, hookMetadata, address(hook) ); @@ -82,7 +84,9 @@ contract HypValue is TokenRouter { address _recipient, uint256 _amount, bytes calldata // no metadata - ) internal virtual override {} + ) internal virtual override { + Address.sendValue(payable(_recipient), _amount); + } function balanceOf( address /* _account */ @@ -93,10 +97,13 @@ contract HypValue is TokenRouter { function _checkSufficientValue( uint32 _destination, uint256 _amount - ) internal view { + ) internal view returns (uint256) { uint256 quote = this.quoteGasPayment(_destination); if (msg.value < _amount + quote) { revert InsufficientValue(_amount + quote, msg.value); } + return quote; } + + receive() external payable {} } diff --git a/solidity/test/token/HypValue.t.sol b/solidity/test/token/HypValue.t.sol index 6be1501163..591e3d63dc 100644 --- a/solidity/test/token/HypValue.t.sol +++ b/solidity/test/token/HypValue.t.sol @@ -1,11 +1,18 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity >=0.8.0; +import "forge-std/console.sol"; + import {TypeCasts} from "../../contracts/libs/TypeCasts.sol"; import {HypTokenTest} from "./HypERC20.t.sol"; +import {HypERC20} from "../../contracts/token/HypERC20.sol"; +import {TokenRouter} from "../../contracts/token/libs/TokenRouter.sol"; import {HypValue} from "../../contracts/token/HypValue.sol"; import {OPL2ToL1Hook} from "../../contracts/hooks/OPL2ToL1Hook.sol"; import {OPL2ToL1Ism} from "../../contracts/isms/hook/OPL2ToL1Ism.sol"; +import {IOptimismPortal} from "../../contracts/interfaces/optimism/IOptimismPortal.sol"; +import {ICrossDomainMessenger} from "../../contracts/interfaces/optimism/ICrossDomainMessenger.sol"; +import {AbstractMessageIdAuthorizedIsm} from "../../contracts/isms/hook/AbstractMessageIdAuthorizedIsm.sol"; import {MockOptimismMessenger, MockOptimismPortal} from "../../contracts/mock/MockOptimism.sol"; import {TestInterchainGasPaymaster} from "../../contracts/test/TestInterchainGasPaymaster.sol"; @@ -15,7 +22,8 @@ contract HypValueTest is HypTokenTest { address internal constant L2_MESSENGER_ADDRESS = 0x4200000000000000000000000000000000000007; - HypValue internal valueRouter; + HypValue internal localValueRouter; + HypValue internal remoteValueRouter; OPL2ToL1Hook internal valueHook; OPL2ToL1Ism internal ism; TestInterchainGasPaymaster internal mockOverheadIgp; @@ -29,8 +37,11 @@ contract HypValueTest is HypTokenTest { address(new MockOptimismMessenger()).code ); - localToken = new HypValue(address(localMailbox)); - valueRouter = HypValue(payable(address(localToken))); + localValueRouter = new HypValue(address(localMailbox)); + remoteValueRouter = new HypValue(address(remoteMailbox)); + + localToken = TokenRouter(payable(address(localValueRouter))); + remoteToken = HypERC20(payable(address(remoteValueRouter))); l1Messenger = new MockOptimismMessenger(); portal = new MockOptimismPortal(); @@ -46,13 +57,22 @@ contract HypValueTest is HypTokenTest { address(mockOverheadIgp) ); - valueRouter.initialize(address(valueHook), address(ism), address(this)); + localValueRouter.initialize( + address(valueHook), + address(ism), + address(this) + ); + remoteValueRouter.initialize( + address(valueHook), + address(ism), + address(this) + ); - valueRouter.enrollRemoteRouter( + localValueRouter.enrollRemoteRouter( DESTINATION, address(remoteToken).addressToBytes32() ); - remoteToken.enrollRemoteRouter( + remoteValueRouter.enrollRemoteRouter( ORIGIN, address(localToken).addressToBytes32() ); @@ -61,18 +81,73 @@ contract HypValueTest is HypTokenTest { } function testRemoteTransfer() public { - // uint256 balanceBefore = localToken.balanceOf(ALICE); - - uint256 quote = valueRouter.quoteGasPayment(DESTINATION); + uint256 quote = localValueRouter.quoteGasPayment(DESTINATION); + console.log("quote", quote); uint256 msgValue = TRANSFER_AMT + quote; - // vm.prank(ALICE); - _performRemoteTransferWithEmit(msgValue, TRANSFER_AMT, quote); - // assertEq(localToken.balanceOf(ALICE), balanceBefore - TRANSFER_AMT); + vm.expectEmit(true, true, false, true); + emit TokenRouter.SentTransferRemote( + DESTINATION, + BOB.addressToBytes32(), + TRANSFER_AMT + ); + + vm.prank(ALICE); + bytes32 messageId = localToken.transferRemote{value: msgValue}( + DESTINATION, + BOB.addressToBytes32(), + TRANSFER_AMT + ); + + _externalBridgeDestinationCall(messageId, msgValue); + + vm.expectEmit(true, true, false, true); + emit ReceivedTransferRemote( + ORIGIN, + BOB.addressToBytes32(), + TRANSFER_AMT + ); + remoteMailbox.processNextInboundMessage(); + + assertEq(BOB.balance, TRANSFER_AMT); + assertEq(address(mockOverheadIgp).balance, quote); } function testTransfer_withHookSpecified( uint256 fee, bytes calldata metadata ) public override {} + + function _externalBridgeDestinationCall( + bytes32 _messageId, + uint256 _msgValue + ) internal { + bytes memory encodedHookData = abi.encodeCall( + AbstractMessageIdAuthorizedIsm.preVerifyMessage, + (_messageId, _msgValue) + ); + + bytes memory messengerCalldata = abi.encodeCall( + ICrossDomainMessenger.relayMessage, + ( + 0, + address(valueHook), + address(ism), + _msgValue, + uint256(100_000), + encodedHookData + ) + ); + vm.deal(address(portal), _msgValue); + IOptimismPortal.WithdrawalTransaction + memory withdrawal = IOptimismPortal.WithdrawalTransaction({ + nonce: 0, + sender: L2_MESSENGER_ADDRESS, + target: address(l1Messenger), + value: _msgValue, + gasLimit: 100_000, + data: messengerCalldata + }); + portal.finalizeWithdrawalTransaction(withdrawal); + } } From f5108634479210d58ce3b5fe638786b146c6099f Mon Sep 17 00:00:00 2001 From: -f Date: Tue, 5 Nov 2024 18:47:57 +0530 Subject: [PATCH 60/61] test done --- solidity/contracts/token/HypValue.sol | 73 ++++++++++++++++++++++++--- solidity/test/token/HypValue.t.sol | 52 +++++++++++++++++-- 2 files changed, 113 insertions(+), 12 deletions(-) diff --git a/solidity/contracts/token/HypValue.sol b/solidity/contracts/token/HypValue.sol index 30d04386e7..3908be868a 100644 --- a/solidity/contracts/token/HypValue.sol +++ b/solidity/contracts/token/HypValue.sol @@ -1,17 +1,44 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity >=0.8.0; +/*@@@@@@@ @@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@ HYPERLANE @@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ +@@@@@@@@@ @@@@@@@@*/ + +// ============ Internal Imports ============ import {TokenRouter} from "./libs/TokenRouter.sol"; -import {TokenMessage} from "./libs/TokenMessage.sol"; import {StandardHookMetadata} from "../hooks/libs/StandardHookMetadata.sol"; + +// ============ External Imports ============ import {Address} from "@openzeppelin/contracts/utils/Address.sol"; -// Note: this assumes 1:1 exchange rate between source and destination chain +/** + * @title HypValue + * @author Abacus Works + * @notice This contract facilitates the transfer of value between chains using value transfer hooks + */ contract HypValue is TokenRouter { + // ============ Errors ============ error InsufficientValue(uint256 amount, uint256 value); constructor(address _mailbox) TokenRouter(_mailbox) {} + // ============ Initialization ============ + + /** + * @notice Initializes the contract + * @param _valuehook The address of the value transfer hook + * @param _interchainSecurityModule The address of the interchain security module + * @param _owner The owner of the contract + */ function initialize( address _valuehook, address _interchainSecurityModule, @@ -24,15 +51,20 @@ contract HypValue is TokenRouter { ); } - // use _hook with caution + // ============ External Functions ============ + + /** + * @inheritdoc TokenRouter + * @dev use _hook with caution, make sure that this hook can handle msg.value transfer using the metadata.msgValue() + */ function transferRemote( uint32 _destination, bytes32 _recipient, uint256 _amount, bytes calldata _hookMetadata, address _hook - ) public payable virtual override returns (bytes32 messageId) { - uint256 quote = _checkSufficientValue(_destination, _amount); + ) external payable virtual override returns (bytes32 messageId) { + uint256 quote = _checkSufficientValue(_destination, _amount, _hook); bytes memory hookMetadata = StandardHookMetadata.overrideMsgValue( _hookMetadata, @@ -50,12 +82,17 @@ contract HypValue is TokenRouter { ); } + /// @inheritdoc TokenRouter function transferRemote( uint32 _destination, bytes32 _recipient, uint256 _amount ) external payable virtual override returns (bytes32 messageId) { - uint256 quote = _checkSufficientValue(_destination, _amount); + uint256 quote = _checkSufficientValue( + _destination, + _amount, + address(hook) + ); bytes memory hookMetadata = StandardHookMetadata.formatMetadata( _amount, destinationGas[_destination], @@ -74,12 +111,22 @@ contract HypValue is TokenRouter { ); } + // ============ Internal Functions ============ + + /** + * @inheritdoc TokenRouter + * @dev No metadata is needed for value transfers + */ function _transferFromSender( uint256 ) internal pure override returns (bytes memory) { return bytes(""); // no metadata } + /** + * @inheritdoc TokenRouter + * @dev Sends the value to the recipient + */ function _transferTo( address _recipient, uint256 _amount, @@ -88,17 +135,27 @@ contract HypValue is TokenRouter { Address.sendValue(payable(_recipient), _amount); } + /** + * @inheritdoc TokenRouter + * @dev This contract doesn't hold value + */ function balanceOf( address /* _account */ ) external pure override returns (uint256) { return 0; } + /// @dev Checks if the provided value is sufficient for the transfer function _checkSufficientValue( uint32 _destination, - uint256 _amount + uint256 _amount, + address _hook ) internal view returns (uint256) { - uint256 quote = this.quoteGasPayment(_destination); + uint256 quote = _GasRouter_quoteDispatch( + _destination, + new bytes(0), + _hook + ); if (msg.value < _amount + quote) { revert InsufficientValue(_amount + quote, msg.value); } diff --git a/solidity/test/token/HypValue.t.sol b/solidity/test/token/HypValue.t.sol index 591e3d63dc..5165a24555 100644 --- a/solidity/test/token/HypValue.t.sol +++ b/solidity/test/token/HypValue.t.sol @@ -1,8 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity >=0.8.0; -import "forge-std/console.sol"; - import {TypeCasts} from "../../contracts/libs/TypeCasts.sol"; import {HypTokenTest} from "./HypERC20.t.sol"; import {HypERC20} from "../../contracts/token/HypERC20.sol"; @@ -11,6 +9,7 @@ import {HypValue} from "../../contracts/token/HypValue.sol"; import {OPL2ToL1Hook} from "../../contracts/hooks/OPL2ToL1Hook.sol"; import {OPL2ToL1Ism} from "../../contracts/isms/hook/OPL2ToL1Ism.sol"; import {IOptimismPortal} from "../../contracts/interfaces/optimism/IOptimismPortal.sol"; +import {TestPostDispatchHook} from "../../contracts/test/TestPostDispatchHook.sol"; import {ICrossDomainMessenger} from "../../contracts/interfaces/optimism/ICrossDomainMessenger.sol"; import {AbstractMessageIdAuthorizedIsm} from "../../contracts/isms/hook/AbstractMessageIdAuthorizedIsm.sol"; import {MockOptimismMessenger, MockOptimismPortal} from "../../contracts/mock/MockOptimism.sol"; @@ -82,7 +81,6 @@ contract HypValueTest is HypTokenTest { function testRemoteTransfer() public { uint256 quote = localValueRouter.quoteGasPayment(DESTINATION); - console.log("quote", quote); uint256 msgValue = TRANSFER_AMT + quote; vm.expectEmit(true, true, false, true); @@ -99,6 +97,9 @@ contract HypValueTest is HypTokenTest { TRANSFER_AMT ); + vm.assertEq(address(localToken).balance, 0); + vm.assertEq(address(valueHook).balance, 0); + _externalBridgeDestinationCall(messageId, msgValue); vm.expectEmit(true, true, false, true); @@ -113,11 +114,54 @@ contract HypValueTest is HypTokenTest { assertEq(address(mockOverheadIgp).balance, quote); } + function testRemoteTransfer_invalidAmount() public { + uint256 quote = localValueRouter.quoteGasPayment(DESTINATION); + + vm.expectRevert( + abi.encodeWithSelector( + HypValue.InsufficientValue.selector, + TRANSFER_AMT + quote, + TRANSFER_AMT + ) + ); + vm.prank(ALICE); + bytes32 messageId = localToken.transferRemote{value: TRANSFER_AMT}( + DESTINATION, + BOB.addressToBytes32(), + TRANSFER_AMT + ); + } + function testTransfer_withHookSpecified( uint256 fee, bytes calldata metadata - ) public override {} + ) public override { + vm.assume(fee < TRANSFER_AMT); + uint256 msgValue = TRANSFER_AMT + fee; + vm.deal(ALICE, msgValue); + + TestPostDispatchHook hook = new TestPostDispatchHook(); + hook.setFee(fee); + + vm.prank(ALICE); + bytes32 messageId = localToken.transferRemote{value: msgValue}( + DESTINATION, + BOB.addressToBytes32(), + TRANSFER_AMT, + metadata, + address(hook) + ); + + vm.assertEq(address(localToken).balance, 0); + vm.assertEq(address(valueHook).balance, 0); + } + + function testBenchmark_overheadGasUsage() public override { + vm.deal(address(localValueRouter), TRANSFER_AMT); + super.testBenchmark_overheadGasUsage(); + } + // helper function to simulate the external bridge destination call using OP L2 -> L1 bridge function _externalBridgeDestinationCall( bytes32 _messageId, uint256 _msgValue From 862d597a68fd787d96eb177b4f802225b930ca00 Mon Sep 17 00:00:00 2001 From: -f Date: Tue, 5 Nov 2024 18:53:40 +0530 Subject: [PATCH 61/61] rm virtual --- solidity/test/token/HypERC20.t.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/solidity/test/token/HypERC20.t.sol b/solidity/test/token/HypERC20.t.sol index 964a5346f9..c3b7da74cd 100644 --- a/solidity/test/token/HypERC20.t.sol +++ b/solidity/test/token/HypERC20.t.sol @@ -352,7 +352,7 @@ contract HypERC20Test is HypTokenTest { assertEq(erc20Token.balanceOf(BOB), 100e18); } - function testRemoteTransfer() public virtual { + function testRemoteTransfer() public { remoteToken.enrollRemoteRouter( ORIGIN, address(localToken).addressToBytes32() @@ -413,7 +413,7 @@ contract HypERC20CollateralTest is HypTokenTest { function testInitialize_revert_ifAlreadyInitialized() public {} - function testRemoteTransfer() public virtual { + function testRemoteTransfer() public { uint256 balanceBefore = localToken.balanceOf(ALICE); vm.prank(ALICE);