diff --git a/.changeset/six-planets-love.md b/.changeset/six-planets-love.md new file mode 100644 index 0000000000..e8d1e62e34 --- /dev/null +++ b/.changeset/six-planets-love.md @@ -0,0 +1,6 @@ +--- +'@hyperlane-xyz/infra': minor +'@hyperlane-xyz/sdk': minor +--- + +Added warp route artifacts type adopting registry schema diff --git a/.github/workflows/agent-release-artifacts.yml b/.github/workflows/agent-release-artifacts.yml index 79c421f22a..2827120989 100644 --- a/.github/workflows/agent-release-artifacts.yml +++ b/.github/workflows/agent-release-artifacts.yml @@ -49,7 +49,7 @@ jobs: run: | sudo apt-get update -qq sudo apt-get install -qq crossbuild-essential-arm64 crossbuild-essential-armhf - + # some additional configuration for cross-compilation on linux cat >>~/.cargo/config < # for scraper ``` @@ -94,6 +97,12 @@ cargo run --release --bin run-locally This will automatically build the agents, start a local node, build and deploy the contracts, and run a relayer and validator. By default, this test will run indefinitely, but can be stopped with `ctrl-c`. +To run the tests for a specific VM, use the `--features` flag. + +```bash +cargo test --release --package run-locally --bin run-locally --features cosmos -- cosmos::test --nocapture +``` + ### Building Agent Docker Images There exists a docker build for the agent binaries. These docker images are used for deploying the agents in a diff --git a/rust/agents/relayer/Cargo.toml b/rust/agents/relayer/Cargo.toml index 0bd5697971..d6eb28a153 100644 --- a/rust/agents/relayer/Cargo.toml +++ b/rust/agents/relayer/Cargo.toml @@ -34,7 +34,7 @@ tokio = { workspace = true, features = ["rt", "macros", "parking_lot"] } tracing-futures.workspace = true tracing.workspace = true -hyperlane-core = { path = "../../hyperlane-core", features = ["agent"] } +hyperlane-core = { path = "../../hyperlane-core", features = ["agent", "fallback-provider"] } hyperlane-base = { path = "../../hyperlane-base" } hyperlane-ethereum = { path = "../../chains/hyperlane-ethereum" } diff --git a/rust/agents/relayer/src/msg/metadata/base.rs b/rust/agents/relayer/src/msg/metadata/base.rs index 65dda6181f..950723e74f 100644 --- a/rust/agents/relayer/src/msg/metadata/base.rs +++ b/rust/agents/relayer/src/msg/metadata/base.rs @@ -381,7 +381,7 @@ impl BaseMetadataBuilder { continue; } - match config.build(None) { + match config.build(None).await { Ok(checkpoint_syncer) => { // found the syncer for this validator checkpoint_syncers.insert(validator.into(), checkpoint_syncer.into()); diff --git a/rust/agents/scraper/migration/src/m20230309_000001_create_table_domain.rs b/rust/agents/scraper/migration/src/m20230309_000001_create_table_domain.rs index afbf4d15c9..46c3c13133 100644 --- a/rust/agents/scraper/migration/src/m20230309_000001_create_table_domain.rs +++ b/rust/agents/scraper/migration/src/m20230309_000001_create_table_domain.rs @@ -222,6 +222,14 @@ const DOMAINS: &[RawDomain] = &[ is_test_net: false, is_deprecated: false, }, + RawDomain { + name: "inevm", + token: "INJ", + domain: 2525, + chain_id: 2525, + is_test_net: false, + is_deprecated: false, + }, RawDomain { name: "test1", token: "ETH", diff --git a/rust/agents/validator/Cargo.toml b/rust/agents/validator/Cargo.toml index f562938db2..bfea4c16a3 100644 --- a/rust/agents/validator/Cargo.toml +++ b/rust/agents/validator/Cargo.toml @@ -24,7 +24,7 @@ tokio = { workspace = true, features = ["rt", "macros", "parking_lot"] } tracing-futures.workspace = true tracing.workspace = true -hyperlane-core = { path = "../../hyperlane-core", features = ["agent"] } +hyperlane-core = { path = "../../hyperlane-core", features = ["agent", "fallback-provider"] } hyperlane-base = { path = "../../hyperlane-base" } hyperlane-ethereum = { path = "../../chains/hyperlane-ethereum" } hyperlane-cosmos = { path = "../../chains/hyperlane-cosmos" } diff --git a/rust/agents/validator/src/validator.rs b/rust/agents/validator/src/validator.rs index a84de1c8d6..fb16a9267d 100644 --- a/rust/agents/validator/src/validator.rs +++ b/rust/agents/validator/src/validator.rs @@ -68,7 +68,7 @@ impl BaseAgent for Validator { let (signer_instance, signer) = SingletonSigner::new(settings.validator.build().await?); let core = settings.build_hyperlane_core(metrics.clone()); - let checkpoint_syncer = settings.checkpoint_syncer.build(None)?.into(); + let checkpoint_syncer = settings.checkpoint_syncer.build(None).await?.into(); let mailbox = settings .build_mailbox(&settings.origin_chain, &metrics) diff --git a/rust/chains/hyperlane-cosmos/Cargo.toml b/rust/chains/hyperlane-cosmos/Cargo.toml index b48aa3590d..c105faec66 100644 --- a/rust/chains/hyperlane-cosmos/Cargo.toml +++ b/rust/chains/hyperlane-cosmos/Cargo.toml @@ -16,12 +16,13 @@ bech32 = { workspace = true } cosmrs = { workspace = true, features = ["cosmwasm", "tokio", "grpc", "rpc"] } derive-new = { workspace = true } hex = { workspace = true } -hpl-interface.workspace = true http = { workspace = true } +hyperlane-cosmwasm-interface.workspace = true hyper = { workspace = true } hyper-tls = { workspace = true } injective-protobuf = { workspace = true } injective-std = { workspace = true } +itertools = { workspace = true } once_cell = { workspace = true } protobuf = { workspace = true } ripemd = { workspace = true } @@ -38,4 +39,4 @@ tracing = { workspace = true } tracing-futures = { workspace = true } url = { workspace = true } -hyperlane-core = { path = "../../hyperlane-core" } +hyperlane-core = { path = "../../hyperlane-core", features = ["fallback-provider"]} diff --git a/rust/chains/hyperlane-cosmos/src/error.rs b/rust/chains/hyperlane-cosmos/src/error.rs index 2c3e1e7475..06fffaff7e 100644 --- a/rust/chains/hyperlane-cosmos/src/error.rs +++ b/rust/chains/hyperlane-cosmos/src/error.rs @@ -1,5 +1,6 @@ use cosmrs::proto::prost; use hyperlane_core::ChainCommunicationError; +use std::fmt::Debug; /// Errors from the crates specific to the hyperlane-cosmos /// implementation. @@ -28,6 +29,9 @@ pub enum HyperlaneCosmosError { /// Tonic error #[error("{0}")] Tonic(#[from] tonic::transport::Error), + /// Tonic codegen error + #[error("{0}")] + TonicGenError(#[from] tonic::codegen::StdError), /// Tendermint RPC Error #[error(transparent)] TendermintError(#[from] tendermint_rpc::error::Error), @@ -37,6 +41,9 @@ pub enum HyperlaneCosmosError { /// Protobuf error #[error("{0}")] Protobuf(#[from] protobuf::ProtobufError), + /// Fallback providers failed + #[error("Fallback providers failed. (Errors: {0:?})")] + FallbackProvidersFailed(Vec), } impl From for ChainCommunicationError { diff --git a/rust/chains/hyperlane-cosmos/src/interchain_security_module.rs b/rust/chains/hyperlane-cosmos/src/interchain_security_module.rs index bfde29f298..dd495be89a 100644 --- a/rust/chains/hyperlane-cosmos/src/interchain_security_module.rs +++ b/rust/chains/hyperlane-cosmos/src/interchain_security_module.rs @@ -82,7 +82,7 @@ impl InterchainSecurityModule for CosmosInterchainSecurityModule { .await?; let module_type_response = - serde_json::from_slice::(&data)?; + serde_json::from_slice::(&data)?; Ok(IsmType(module_type_response.typ).into()) } diff --git a/rust/chains/hyperlane-cosmos/src/lib.rs b/rust/chains/hyperlane-cosmos/src/lib.rs index 82a4a0ece1..c0ce3ad549 100644 --- a/rust/chains/hyperlane-cosmos/src/lib.rs +++ b/rust/chains/hyperlane-cosmos/src/lib.rs @@ -16,6 +16,7 @@ mod multisig_ism; mod payloads; mod providers; mod routing_ism; +mod rpc_clients; mod signers; mod trait_builder; mod types; diff --git a/rust/chains/hyperlane-cosmos/src/payloads/aggregate_ism.rs b/rust/chains/hyperlane-cosmos/src/payloads/aggregate_ism.rs index 8276675ff7..23bb35a8f8 100644 --- a/rust/chains/hyperlane-cosmos/src/payloads/aggregate_ism.rs +++ b/rust/chains/hyperlane-cosmos/src/payloads/aggregate_ism.rs @@ -1,11 +1,11 @@ use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct VerifyRequest { pub verify: VerifyRequestInner, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct VerifyRequestInner { pub metadata: String, pub message: String, diff --git a/rust/chains/hyperlane-cosmos/src/payloads/general.rs b/rust/chains/hyperlane-cosmos/src/payloads/general.rs index 488cae2d37..af2a4b0b6e 100644 --- a/rust/chains/hyperlane-cosmos/src/payloads/general.rs +++ b/rust/chains/hyperlane-cosmos/src/payloads/general.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct EmptyStruct {} #[derive(Serialize, Deserialize, Debug, Clone)] diff --git a/rust/chains/hyperlane-cosmos/src/payloads/ism_routes.rs b/rust/chains/hyperlane-cosmos/src/payloads/ism_routes.rs index 052a1cc48b..4a0563945f 100644 --- a/rust/chains/hyperlane-cosmos/src/payloads/ism_routes.rs +++ b/rust/chains/hyperlane-cosmos/src/payloads/ism_routes.rs @@ -1,12 +1,12 @@ use super::general::EmptyStruct; use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct IsmRouteRequest { pub route: IsmRouteRequestInner, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct IsmRouteRequestInner { pub message: String, // hexbinary } @@ -16,22 +16,22 @@ pub struct IsmRouteRespnose { pub ism: String, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct QueryRoutingIsmGeneralRequest { pub routing_ism: T, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct QueryRoutingIsmRouteResponse { pub ism: String, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct QueryIsmGeneralRequest { pub ism: T, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct QueryIsmModuleTypeRequest { pub module_type: EmptyStruct, } @@ -39,5 +39,5 @@ pub struct QueryIsmModuleTypeRequest { #[derive(Serialize, Deserialize, Debug)] pub struct QueryIsmModuleTypeResponse { #[serde(rename = "type")] - pub typ: hpl_interface::ism::IsmType, + pub typ: hyperlane_cosmwasm_interface::ism::IsmType, } diff --git a/rust/chains/hyperlane-cosmos/src/payloads/mailbox.rs b/rust/chains/hyperlane-cosmos/src/payloads/mailbox.rs index 145ba5b16c..75eef04595 100644 --- a/rust/chains/hyperlane-cosmos/src/payloads/mailbox.rs +++ b/rust/chains/hyperlane-cosmos/src/payloads/mailbox.rs @@ -3,52 +3,52 @@ use serde::{Deserialize, Serialize}; use super::general::EmptyStruct; // Requests -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct GeneralMailboxQuery { pub mailbox: T, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct CountRequest { pub count: EmptyStruct, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct NonceRequest { pub nonce: EmptyStruct, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct RecipientIsmRequest { pub recipient_ism: RecipientIsmRequestInner, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct RecipientIsmRequestInner { pub recipient_addr: String, // hexbinary } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct DefaultIsmRequest { pub default_ism: EmptyStruct, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct DeliveredRequest { pub message_delivered: DeliveredRequestInner, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct DeliveredRequestInner { pub id: String, // hexbinary } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct ProcessMessageRequest { pub process: ProcessMessageRequestInner, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct ProcessMessageRequestInner { pub metadata: String, pub message: String, diff --git a/rust/chains/hyperlane-cosmos/src/payloads/merkle_tree_hook.rs b/rust/chains/hyperlane-cosmos/src/payloads/merkle_tree_hook.rs index 7635f0ef72..e960628771 100644 --- a/rust/chains/hyperlane-cosmos/src/payloads/merkle_tree_hook.rs +++ b/rust/chains/hyperlane-cosmos/src/payloads/merkle_tree_hook.rs @@ -4,24 +4,24 @@ use super::general::EmptyStruct; const TREE_DEPTH: usize = 32; -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct MerkleTreeGenericRequest { pub merkle_hook: T, } // --------- Requests --------- -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct MerkleTreeRequest { pub tree: EmptyStruct, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct MerkleTreeCountRequest { pub count: EmptyStruct, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct CheckPointRequest { pub check_point: EmptyStruct, } diff --git a/rust/chains/hyperlane-cosmos/src/payloads/multisig_ism.rs b/rust/chains/hyperlane-cosmos/src/payloads/multisig_ism.rs index 204e726dc7..c56588d1d6 100644 --- a/rust/chains/hyperlane-cosmos/src/payloads/multisig_ism.rs +++ b/rust/chains/hyperlane-cosmos/src/payloads/multisig_ism.rs @@ -1,11 +1,11 @@ use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct VerifyInfoRequest { pub verify_info: VerifyInfoRequestInner, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct VerifyInfoRequestInner { pub message: String, // hexbinary } diff --git a/rust/chains/hyperlane-cosmos/src/payloads/validator_announce.rs b/rust/chains/hyperlane-cosmos/src/payloads/validator_announce.rs index fdf449c7c4..cf4e5eb1f8 100644 --- a/rust/chains/hyperlane-cosmos/src/payloads/validator_announce.rs +++ b/rust/chains/hyperlane-cosmos/src/payloads/validator_announce.rs @@ -2,17 +2,17 @@ use serde::{Deserialize, Serialize}; use super::general::EmptyStruct; -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct GetAnnouncedValidatorsRequest { pub get_announced_validators: EmptyStruct, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct GetAnnounceStorageLocationsRequest { pub get_announce_storage_locations: GetAnnounceStorageLocationsRequestInner, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct GetAnnounceStorageLocationsRequestInner { pub validators: Vec, } diff --git a/rust/chains/hyperlane-cosmos/src/providers/grpc.rs b/rust/chains/hyperlane-cosmos/src/providers/grpc.rs index 3594c7398e..a6bc070aba 100644 --- a/rust/chains/hyperlane-cosmos/src/providers/grpc.rs +++ b/rust/chains/hyperlane-cosmos/src/providers/grpc.rs @@ -24,7 +24,9 @@ use cosmrs::{ tx::{self, Fee, MessageExt, SignDoc, SignerInfo}, Any, Coin, }; +use derive_new::new; use hyperlane_core::{ + rpc_clients::{BlockNumberGetter, FallbackProvider}, ChainCommunicationError, ChainResult, ContractLocator, FixedPointNumber, HyperlaneDomain, U256, }; use protobuf::Message as _; @@ -33,9 +35,10 @@ use tonic::{ transport::{Channel, Endpoint}, GrpcMethod, IntoRequest, }; +use url::Url; -use crate::HyperlaneCosmosError; use crate::{address::CosmosAddress, CosmosAmount}; +use crate::{rpc_clients::CosmosFallbackProvider, HyperlaneCosmosError}; use crate::{signers::Signer, ConnectionConf}; /// A multiplier applied to a simulated transaction's gas usage to @@ -45,6 +48,36 @@ const GAS_ESTIMATE_MULTIPLIER: f64 = 1.25; /// be valid for. const TIMEOUT_BLOCKS: u64 = 1000; +#[derive(Debug, Clone, new)] +struct CosmosChannel { + channel: Channel, + /// The url that this channel is connected to. + /// Not explicitly used, but useful for debugging. + _url: Url, +} + +#[async_trait] +impl BlockNumberGetter for CosmosChannel { + async fn get_block_number(&self) -> Result { + let mut client = ServiceClient::new(self.channel.clone()); + let request = tonic::Request::new(GetLatestBlockRequest {}); + + let response = client + .get_latest_block(request) + .await + .map_err(ChainCommunicationError::from_other)? + .into_inner(); + let height = response + .block + .ok_or_else(|| ChainCommunicationError::from_other_str("block not present"))? + .header + .ok_or_else(|| ChainCommunicationError::from_other_str("header not present"))? + .height; + + Ok(height as u64) + } +} + #[async_trait] /// Cosmwasm GRPC Provider pub trait WasmProvider: Send + Sync { @@ -56,14 +89,14 @@ pub trait WasmProvider: Send + Sync { async fn latest_block_height(&self) -> ChainResult; /// Perform a wasm query against the stored contract address. - async fn wasm_query( + async fn wasm_query( &self, payload: T, block_height: Option, ) -> ChainResult>; /// Perform a wasm query against a specified contract address. - async fn wasm_query_to( + async fn wasm_query_to( &self, to: String, payload: T, @@ -71,14 +104,17 @@ pub trait WasmProvider: Send + Sync { ) -> ChainResult>; /// Send a wasm tx. - async fn wasm_send( + async fn wasm_send( &self, payload: T, gas_limit: Option, ) -> ChainResult; /// Estimate gas for a wasm tx. - async fn wasm_estimate_gas(&self, payload: T) -> ChainResult; + async fn wasm_estimate_gas( + &self, + payload: T, + ) -> ChainResult; } #[derive(Debug, Clone)] @@ -95,7 +131,7 @@ pub struct WasmGrpcProvider { signer: Option, /// GRPC Channel that can be cheaply cloned. /// See `` - channel: Channel, + provider: CosmosFallbackProvider, gas_price: CosmosAmount, } @@ -108,9 +144,21 @@ impl WasmGrpcProvider { locator: Option, signer: Option, ) -> ChainResult { - let endpoint = - Endpoint::new(conf.get_grpc_url()).map_err(Into::::into)?; - let channel = endpoint.connect_lazy(); + // get all the configured grpc urls and convert them to a Vec + let channels: Result, _> = conf + .get_grpc_urls() + .into_iter() + .map(|url| { + Endpoint::new(url.to_string()) + .map(|e| CosmosChannel::new(e.connect_lazy(), url)) + .map_err(Into::::into) + }) + .collect(); + let mut builder = FallbackProvider::builder(); + builder = builder.add_providers(channels?); + let fallback_provider = builder.build(); + let provider = CosmosFallbackProvider::new(fallback_provider); + let contract_address = locator .map(|l| { CosmosAddress::from_h256( @@ -126,7 +174,7 @@ impl WasmGrpcProvider { conf, contract_address, signer, - channel, + provider, gas_price, }) } @@ -225,21 +273,36 @@ impl WasmGrpcProvider { // https://github.com/cosmos/cosmjs/blob/44893af824f0712d1f406a8daa9fcae335422235/packages/stargate/src/modules/tx/queries.ts#L67 signatures: vec![vec![]], }; - - let mut client = TxServiceClient::new(self.channel.clone()); let tx_bytes = raw_tx .to_bytes() .map_err(ChainCommunicationError::from_other)?; - #[allow(deprecated)] - let sim_req = tonic::Request::new(SimulateRequest { tx: None, tx_bytes }); - let gas_used = client - .simulate(sim_req) - .await - .map_err(ChainCommunicationError::from_other)? - .into_inner() - .gas_info - .ok_or_else(|| ChainCommunicationError::from_other_str("gas info not present"))? - .gas_used; + let gas_used = self + .provider + .call(move |provider| { + let tx_bytes_clone = tx_bytes.clone(); + let future = async move { + let mut client = TxServiceClient::new(provider.channel.clone()); + #[allow(deprecated)] + let sim_req = tonic::Request::new(SimulateRequest { + tx: None, + tx_bytes: tx_bytes_clone, + }); + let gas_used = client + .simulate(sim_req) + .await + .map_err(ChainCommunicationError::from_other)? + .into_inner() + .gas_info + .ok_or_else(|| { + ChainCommunicationError::from_other_str("gas info not present") + })? + .gas_used; + + Ok(gas_used) + }; + Box::pin(future) + }) + .await?; let gas_estimate = (gas_used as f64 * GAS_ESTIMATE_MULTIPLIER) as u64; @@ -248,14 +311,25 @@ impl WasmGrpcProvider { /// Fetches balance for a given `address` and `denom` pub async fn get_balance(&self, address: String, denom: String) -> ChainResult { - let mut client = QueryBalanceClient::new(self.channel.clone()); - - let balance_request = tonic::Request::new(QueryBalanceRequest { address, denom }); - let response = client - .balance(balance_request) - .await - .map_err(ChainCommunicationError::from_other)? - .into_inner(); + let response = self + .provider + .call(move |provider| { + let address = address.clone(); + let denom = denom.clone(); + let future = async move { + let mut client = QueryBalanceClient::new(provider.channel.clone()); + let balance_request = + tonic::Request::new(QueryBalanceRequest { address, denom }); + let response = client + .balance(balance_request) + .await + .map_err(ChainCommunicationError::from_other)? + .into_inner(); + Ok(response) + }; + Box::pin(future) + }) + .await?; let balance = response .balance @@ -272,14 +346,23 @@ impl WasmGrpcProvider { return self.account_query_injective(account).await; } - let mut client = QueryAccountClient::new(self.channel.clone()); - - let request = tonic::Request::new(QueryAccountRequest { address: account }); - let response = client - .account(request) - .await - .map_err(ChainCommunicationError::from_other)? - .into_inner(); + let response = self + .provider + .call(move |provider| { + let address = account.clone(); + let future = async move { + let mut client = QueryAccountClient::new(provider.channel.clone()); + let request = tonic::Request::new(QueryAccountRequest { address }); + let response = client + .account(request) + .await + .map_err(ChainCommunicationError::from_other)? + .into_inner(); + Ok(response) + }; + Box::pin(future) + }) + .await?; let account = BaseAccount::decode( response @@ -294,32 +377,46 @@ impl WasmGrpcProvider { /// Injective-specific logic for querying an account. async fn account_query_injective(&self, account: String) -> ChainResult { - let request = tonic::Request::new( - injective_std::types::cosmos::auth::v1beta1::QueryAccountRequest { address: account }, - ); - - // Borrowed from the logic of `QueryAccountClient` in `cosmrs`, but using injective types. - - let mut grpc_client = tonic::client::Grpc::new(self.channel.clone()); - grpc_client - .ready() - .await - .map_err(Into::::into)?; - - let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static("/cosmos.auth.v1beta1.Query/Account"); - let mut req: tonic::Request< - injective_std::types::cosmos::auth::v1beta1::QueryAccountRequest, - > = request.into_request(); - req.extensions_mut() - .insert(GrpcMethod::new("cosmos.auth.v1beta1.Query", "Account")); - - let response: tonic::Response< - injective_std::types::cosmos::auth::v1beta1::QueryAccountResponse, - > = grpc_client - .unary(req, path, codec) - .await - .map_err(Into::::into)?; + let response = self + .provider + .call(move |provider| { + let address = account.clone(); + let future = async move { + let request = tonic::Request::new( + injective_std::types::cosmos::auth::v1beta1::QueryAccountRequest { + address, + }, + ); + + // Borrowed from the logic of `QueryAccountClient` in `cosmrs`, but using injective types. + + let mut grpc_client = tonic::client::Grpc::new(provider.channel.clone()); + grpc_client + .ready() + .await + .map_err(Into::::into)?; + + let codec = tonic::codec::ProstCodec::default(); + let path = + http::uri::PathAndQuery::from_static("/cosmos.auth.v1beta1.Query/Account"); + let mut req: tonic::Request< + injective_std::types::cosmos::auth::v1beta1::QueryAccountRequest, + > = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("cosmos.auth.v1beta1.Query", "Account")); + + let response: tonic::Response< + injective_std::types::cosmos::auth::v1beta1::QueryAccountResponse, + > = grpc_client + .unary(req, path, codec) + .await + .map_err(Into::::into)?; + + Ok(response) + }; + Box::pin(future) + }) + .await?; let mut eth_account = injective_protobuf::proto::account::EthAccount::parse_from_bytes( response @@ -349,14 +446,23 @@ impl WasmGrpcProvider { #[async_trait] impl WasmProvider for WasmGrpcProvider { async fn latest_block_height(&self) -> ChainResult { - let mut client = ServiceClient::new(self.channel.clone()); - let request = tonic::Request::new(GetLatestBlockRequest {}); + let response = self + .provider + .call(move |provider| { + let future = async move { + let mut client = ServiceClient::new(provider.channel.clone()); + let request = tonic::Request::new(GetLatestBlockRequest {}); + let response = client + .get_latest_block(request) + .await + .map_err(ChainCommunicationError::from_other)? + .into_inner(); + Ok(response) + }; + Box::pin(future) + }) + .await?; - let response = client - .get_latest_block(request) - .await - .map_err(ChainCommunicationError::from_other)? - .into_inner(); let height = response .block .ok_or_else(|| ChainCommunicationError::from_other_str("block not present"))? @@ -369,7 +475,7 @@ impl WasmProvider for WasmGrpcProvider { async fn wasm_query(&self, payload: T, block_height: Option) -> ChainResult> where - T: Serialize + Send + Sync, + T: Serialize + Send + Sync + Clone, { let contract_address = self.contract_address.as_ref().ok_or_else(|| { ChainCommunicationError::from_other_str("No contract address available") @@ -385,39 +491,48 @@ impl WasmProvider for WasmGrpcProvider { block_height: Option, ) -> ChainResult> where - T: Serialize + Send + Sync, + T: Serialize + Send + Sync + Clone, { - let mut client = WasmQueryClient::new(self.channel.clone()); - let mut request = tonic::Request::new(QuerySmartContractStateRequest { - address: to, - query_data: serde_json::to_string(&payload)?.as_bytes().to_vec(), - }); - - if let Some(block_height) = block_height { - request - .metadata_mut() - .insert("x-cosmos-block-height", block_height.into()); - } - - let response = client - .smart_contract_state(request) - .await - .map_err(ChainCommunicationError::from_other)? - .into_inner(); + let query_data = serde_json::to_string(&payload)?.as_bytes().to_vec(); + let response = self + .provider + .call(move |provider| { + let to = to.clone(); + let query_data = query_data.clone(); + let future = async move { + let mut client = WasmQueryClient::new(provider.channel.clone()); + + let mut request = tonic::Request::new(QuerySmartContractStateRequest { + address: to, + query_data, + }); + if let Some(block_height) = block_height { + request + .metadata_mut() + .insert("x-cosmos-block-height", block_height.into()); + } + let response = client + .smart_contract_state(request) + .await + .map_err(ChainCommunicationError::from_other)? + .into_inner(); + Ok(response) + }; + Box::pin(future) + }) + .await?; Ok(response.data) } async fn wasm_send(&self, payload: T, gas_limit: Option) -> ChainResult where - T: Serialize + Send + Sync, + T: Serialize + Send + Sync + Clone, { let signer = self.get_signer()?; - let mut client = TxServiceClient::new(self.channel.clone()); let contract_address = self.contract_address.as_ref().ok_or_else(|| { ChainCommunicationError::from_other_str("No contract address available") })?; - let msgs = vec![MsgExecuteContract { sender: signer.address.clone(), contract: contract_address.address(), @@ -426,9 +541,6 @@ impl WasmProvider for WasmGrpcProvider { } .to_any() .map_err(ChainCommunicationError::from_other)?]; - - // We often use U256s to represent gas limits, but Cosmos expects u64s. Try to convert, - // and if it fails, just fallback to None which will result in gas estimation. let gas_limit: Option = gas_limit.and_then(|limit| match limit.try_into() { Ok(limit) => Some(limit), Err(err) => { @@ -439,20 +551,30 @@ impl WasmProvider for WasmGrpcProvider { None } }); - - let tx_req = BroadcastTxRequest { - tx_bytes: self.generate_raw_signed_tx(msgs, gas_limit).await?, - mode: BroadcastMode::Sync as i32, - }; - - let tx_res = client - .broadcast_tx(tx_req) - .await - .map_err(Into::::into)? - .into_inner() - .tx_response - .ok_or_else(|| ChainCommunicationError::from_other_str("Empty tx_response"))?; - + let tx_bytes = self.generate_raw_signed_tx(msgs, gas_limit).await?; + let tx_res = self + .provider + .call(move |provider| { + let tx_bytes = tx_bytes.clone(); + let future = async move { + let mut client = TxServiceClient::new(provider.channel.clone()); + // We often use U256s to represent gas limits, but Cosmos expects u64s. Try to convert, + // and if it fails, just fallback to None which will result in gas estimation. + let tx_req = BroadcastTxRequest { + tx_bytes, + mode: BroadcastMode::Sync as i32, + }; + client + .broadcast_tx(tx_req) + .await + .map_err(Into::::into)? + .into_inner() + .tx_response + .ok_or_else(|| ChainCommunicationError::from_other_str("Empty tx_response")) + }; + Box::pin(future) + }) + .await?; Ok(tx_res) } @@ -482,3 +604,10 @@ impl WasmProvider for WasmGrpcProvider { Ok(response) } } + +#[async_trait] +impl BlockNumberGetter for WasmGrpcProvider { + async fn get_block_number(&self) -> Result { + self.latest_block_height().await + } +} diff --git a/rust/chains/hyperlane-cosmos/src/rpc_clients/fallback.rs b/rust/chains/hyperlane-cosmos/src/rpc_clients/fallback.rs new file mode 100644 index 0000000000..cf933ee73d --- /dev/null +++ b/rust/chains/hyperlane-cosmos/src/rpc_clients/fallback.rs @@ -0,0 +1,140 @@ +use std::{ + fmt::{Debug, Formatter}, + ops::Deref, +}; + +use derive_new::new; +use hyperlane_core::rpc_clients::FallbackProvider; + +/// Wrapper of `FallbackProvider` for use in `hyperlane-cosmos` +#[derive(new, Clone)] +pub struct CosmosFallbackProvider { + fallback_provider: FallbackProvider, +} + +impl Deref for CosmosFallbackProvider { + type Target = FallbackProvider; + + fn deref(&self) -> &Self::Target { + &self.fallback_provider + } +} + +impl Debug for CosmosFallbackProvider +where + C: Debug, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + self.fallback_provider.fmt(f) + } +} + +#[cfg(test)] +mod tests { + use std::time::Duration; + + use async_trait::async_trait; + use hyperlane_core::rpc_clients::test::ProviderMock; + use hyperlane_core::rpc_clients::{BlockNumberGetter, FallbackProviderBuilder}; + use hyperlane_core::ChainCommunicationError; + use tokio::time::sleep; + + use super::*; + + #[derive(Debug, Clone)] + struct CosmosProviderMock(ProviderMock); + + impl Deref for CosmosProviderMock { + type Target = ProviderMock; + + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + impl Default for CosmosProviderMock { + fn default() -> Self { + Self(ProviderMock::default()) + } + } + + impl CosmosProviderMock { + fn new(request_sleep: Option) -> Self { + Self(ProviderMock::new(request_sleep)) + } + } + + #[async_trait] + impl BlockNumberGetter for CosmosProviderMock { + async fn get_block_number(&self) -> Result { + Ok(0) + } + } + + impl Into> for CosmosProviderMock { + fn into(self) -> Box { + Box::new(self) + } + } + + impl CosmosFallbackProvider { + async fn low_level_test_call(&mut self) -> Result<(), ChainCommunicationError> { + self.call(|provider| { + provider.push("GET", "http://localhost:1234"); + let future = async move { + let body = tonic::body::BoxBody::default(); + let response = http::Response::builder().status(200).body(body).unwrap(); + if let Some(sleep_duration) = provider.request_sleep() { + sleep(sleep_duration).await; + } + Ok(response) + }; + Box::pin(future) + }) + .await?; + Ok(()) + } + } + + #[tokio::test] + async fn test_first_provider_is_attempted() { + let fallback_provider_builder = FallbackProviderBuilder::default(); + let providers = vec![ + CosmosProviderMock::default(), + CosmosProviderMock::default(), + CosmosProviderMock::default(), + ]; + let fallback_provider = fallback_provider_builder.add_providers(providers).build(); + let mut cosmos_fallback_provider = CosmosFallbackProvider::new(fallback_provider); + cosmos_fallback_provider + .low_level_test_call() + .await + .unwrap(); + let provider_call_count: Vec<_> = + ProviderMock::get_call_counts(&cosmos_fallback_provider).await; + assert_eq!(provider_call_count, vec![1, 0, 0]); + } + + #[tokio::test] + async fn test_one_stalled_provider() { + let fallback_provider_builder = FallbackProviderBuilder::default(); + let providers = vec![ + CosmosProviderMock::new(Some(Duration::from_millis(10))), + CosmosProviderMock::default(), + CosmosProviderMock::default(), + ]; + let fallback_provider = fallback_provider_builder + .add_providers(providers) + .with_max_block_time(Duration::from_secs(0)) + .build(); + let mut cosmos_fallback_provider = CosmosFallbackProvider::new(fallback_provider); + cosmos_fallback_provider + .low_level_test_call() + .await + .unwrap(); + + let provider_call_count: Vec<_> = + ProviderMock::get_call_counts(&cosmos_fallback_provider).await; + assert_eq!(provider_call_count, vec![0, 0, 1]); + } +} diff --git a/rust/chains/hyperlane-cosmos/src/rpc_clients/mod.rs b/rust/chains/hyperlane-cosmos/src/rpc_clients/mod.rs new file mode 100644 index 0000000000..536845688d --- /dev/null +++ b/rust/chains/hyperlane-cosmos/src/rpc_clients/mod.rs @@ -0,0 +1,3 @@ +pub use self::fallback::*; + +mod fallback; diff --git a/rust/chains/hyperlane-cosmos/src/trait_builder.rs b/rust/chains/hyperlane-cosmos/src/trait_builder.rs index 2bacb4d2f5..1bb3627b9d 100644 --- a/rust/chains/hyperlane-cosmos/src/trait_builder.rs +++ b/rust/chains/hyperlane-cosmos/src/trait_builder.rs @@ -2,12 +2,13 @@ use std::str::FromStr; use derive_new::new; use hyperlane_core::{ChainCommunicationError, FixedPointNumber}; +use url::Url; /// Cosmos connection configuration #[derive(Debug, Clone)] pub struct ConnectionConf { /// The GRPC url to connect to - grpc_url: String, + grpc_urls: Vec, /// The RPC url to connect to rpc_url: String, /// The chain ID @@ -76,8 +77,8 @@ pub enum ConnectionConfError { impl ConnectionConf { /// Get the GRPC url - pub fn get_grpc_url(&self) -> String { - self.grpc_url.clone() + pub fn get_grpc_urls(&self) -> Vec { + self.grpc_urls.clone() } /// Get the RPC url @@ -112,7 +113,7 @@ impl ConnectionConf { /// Create a new connection configuration pub fn new( - grpc_url: String, + grpc_urls: Vec, rpc_url: String, chain_id: String, bech32_prefix: String, @@ -121,7 +122,7 @@ impl ConnectionConf { contract_address_bytes: usize, ) -> Self { Self { - grpc_url, + grpc_urls, rpc_url, chain_id, bech32_prefix, diff --git a/rust/chains/hyperlane-cosmos/src/types.rs b/rust/chains/hyperlane-cosmos/src/types.rs index d7647e7ddf..aa5a954650 100644 --- a/rust/chains/hyperlane-cosmos/src/types.rs +++ b/rust/chains/hyperlane-cosmos/src/types.rs @@ -1,10 +1,10 @@ use cosmrs::proto::cosmos::base::abci::v1beta1::TxResponse; use hyperlane_core::{ChainResult, ModuleType, TxOutcome, H256, U256}; -pub struct IsmType(pub hpl_interface::ism::IsmType); +pub struct IsmType(pub hyperlane_cosmwasm_interface::ism::IsmType); -impl From for IsmType { - fn from(value: hpl_interface::ism::IsmType) -> Self { +impl From for IsmType { + fn from(value: hyperlane_cosmwasm_interface::ism::IsmType) -> Self { IsmType(value) } } @@ -12,14 +12,20 @@ impl From for IsmType { impl From for ModuleType { fn from(value: IsmType) -> Self { match value.0 { - hpl_interface::ism::IsmType::Unused => ModuleType::Unused, - hpl_interface::ism::IsmType::Routing => ModuleType::Routing, - hpl_interface::ism::IsmType::Aggregation => ModuleType::Aggregation, - hpl_interface::ism::IsmType::LegacyMultisig => ModuleType::MessageIdMultisig, - hpl_interface::ism::IsmType::MerkleRootMultisig => ModuleType::MerkleRootMultisig, - hpl_interface::ism::IsmType::MessageIdMultisig => ModuleType::MessageIdMultisig, - hpl_interface::ism::IsmType::Null => ModuleType::Null, - hpl_interface::ism::IsmType::CcipRead => ModuleType::CcipRead, + hyperlane_cosmwasm_interface::ism::IsmType::Unused => ModuleType::Unused, + hyperlane_cosmwasm_interface::ism::IsmType::Routing => ModuleType::Routing, + hyperlane_cosmwasm_interface::ism::IsmType::Aggregation => ModuleType::Aggregation, + hyperlane_cosmwasm_interface::ism::IsmType::LegacyMultisig => { + ModuleType::MessageIdMultisig + } + hyperlane_cosmwasm_interface::ism::IsmType::MerkleRootMultisig => { + ModuleType::MerkleRootMultisig + } + hyperlane_cosmwasm_interface::ism::IsmType::MessageIdMultisig => { + ModuleType::MessageIdMultisig + } + hyperlane_cosmwasm_interface::ism::IsmType::Null => ModuleType::Null, + hyperlane_cosmwasm_interface::ism::IsmType::CcipRead => ModuleType::CcipRead, } } } diff --git a/rust/chains/hyperlane-ethereum/Cargo.toml b/rust/chains/hyperlane-ethereum/Cargo.toml index 8d6db17f45..a72855a00b 100644 --- a/rust/chains/hyperlane-ethereum/Cargo.toml +++ b/rust/chains/hyperlane-ethereum/Cargo.toml @@ -30,7 +30,7 @@ tracing-futures.workspace = true tracing.workspace = true url.workspace = true -hyperlane-core = { path = "../../hyperlane-core" } +hyperlane-core = { path = "../../hyperlane-core", features = ["fallback-provider"]} ethers-prometheus = { path = "../../ethers-prometheus", features = ["serde"] } [build-dependencies] diff --git a/rust/chains/hyperlane-ethereum/src/rpc_clients/fallback.rs b/rust/chains/hyperlane-ethereum/src/rpc_clients/fallback.rs index 2ac6ae009f..1243ecc106 100644 --- a/rust/chains/hyperlane-ethereum/src/rpc_clients/fallback.rs +++ b/rust/chains/hyperlane-ethereum/src/rpc_clients/fallback.rs @@ -12,23 +12,23 @@ use serde_json::Value; use tokio::time::sleep; use tracing::{instrument, warn_span}; -use ethers_prometheus::json_rpc_client::PrometheusJsonRpcClientConfigExt; +use ethers_prometheus::json_rpc_client::{JsonRpcBlockGetter, PrometheusJsonRpcClientConfigExt}; use crate::rpc_clients::{categorize_client_response, CategorizedResponse}; /// Wrapper of `FallbackProvider` for use in `hyperlane-ethereum` #[derive(new)] -pub struct EthereumFallbackProvider(FallbackProvider); +pub struct EthereumFallbackProvider(FallbackProvider); -impl Deref for EthereumFallbackProvider { - type Target = FallbackProvider; +impl Deref for EthereumFallbackProvider { + type Target = FallbackProvider; fn deref(&self) -> &Self::Target { &self.0 } } -impl Debug for EthereumFallbackProvider +impl Debug for EthereumFallbackProvider where C: JsonRpcClient + PrometheusJsonRpcClientConfigExt, { @@ -73,16 +73,17 @@ impl From for ProviderError { #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait)] -impl JsonRpcClient for EthereumFallbackProvider +impl JsonRpcClient for EthereumFallbackProvider> where C: JsonRpcClient + + Into> + PrometheusJsonRpcClientConfigExt - + Into> + Clone, + JsonRpcBlockGetter: BlockNumberGetter, { type Error = ProviderError; - // TODO: Refactor the reusable parts of this function when implementing the cosmos-specific logic + // TODO: Refactor to use `FallbackProvider::call` #[instrument] async fn request(&self, method: &str, params: T) -> Result where @@ -108,7 +109,7 @@ where let resp = fut.await; self.handle_stalled_provider(priority, provider).await; let _span = - warn_span!("request_with_fallback", fallback_count=%idx, provider_index=%priority.index, ?provider).entered(); + warn_span!("request", fallback_count=%idx, provider_index=%priority.index, ?provider).entered(); match categorize_client_response(method, resp) { IsOk(v) => return Ok(serde_json::from_value(v)?), @@ -125,41 +126,37 @@ where #[cfg(test)] mod tests { use ethers_prometheus::json_rpc_client::{JsonRpcBlockGetter, BLOCK_NUMBER_RPC}; + use hyperlane_core::rpc_clients::test::ProviderMock; use hyperlane_core::rpc_clients::FallbackProviderBuilder; use super::*; - use std::sync::{Arc, Mutex}; #[derive(Debug, Clone)] - struct ProviderMock { - // Store requests as tuples of (method, params) - // Even if the tests were single-threaded, need the arc-mutex - // for interior mutability in `JsonRpcClient::request` - requests: Arc>>, - } + struct EthereumProviderMock(ProviderMock); - impl ProviderMock { - fn new() -> Self { - Self { - requests: Arc::new(Mutex::new(vec![])), - } + impl Deref for EthereumProviderMock { + type Target = ProviderMock; + + fn deref(&self) -> &Self::Target { + &self.0 } + } - fn push(&self, method: &str, params: T) { - self.requests - .lock() - .unwrap() - .push((method.to_owned(), format!("{:?}", params))); + impl Default for EthereumProviderMock { + fn default() -> Self { + Self(ProviderMock::default()) } + } - fn requests(&self) -> Vec<(String, String)> { - self.requests.lock().unwrap().clone() + impl EthereumProviderMock { + fn new(request_sleep: Option) -> Self { + Self(ProviderMock::new(request_sleep)) } } - impl Into> for ProviderMock { - fn into(self) -> Box { - Box::new(JsonRpcBlockGetter::new(self.clone())) + impl Into> for EthereumProviderMock { + fn into(self) -> JsonRpcBlockGetter { + JsonRpcBlockGetter::new(self) } } @@ -171,7 +168,7 @@ mod tests { } #[async_trait] - impl JsonRpcClient for ProviderMock { + impl JsonRpcClient for EthereumProviderMock { type Error = HttpClientError; /// Pushes the `(method, params)` to the back of the `requests` queue, @@ -182,12 +179,14 @@ mod tests { params: T, ) -> Result { self.push(method, params); - sleep(Duration::from_millis(10)).await; + if let Some(sleep_duration) = self.request_sleep() { + sleep(sleep_duration).await; + } dummy_return_value() } } - impl PrometheusJsonRpcClientConfigExt for ProviderMock { + impl PrometheusJsonRpcClientConfigExt for EthereumProviderMock { fn node_host(&self) -> &str { todo!() } @@ -197,35 +196,32 @@ mod tests { } } - async fn get_call_counts(fallback_provider: &FallbackProvider) -> Vec { - fallback_provider - .inner - .priorities - .read() - .await - .iter() - .map(|p| { - let provider = &fallback_provider.inner.providers[p.index]; - provider.requests().len() - }) - .collect() + impl EthereumFallbackProvider> + where + C: JsonRpcClient + + PrometheusJsonRpcClientConfigExt + + Into> + + Clone, + JsonRpcBlockGetter: BlockNumberGetter, + { + async fn low_level_test_call(&self) { + self.request::<_, u64>(BLOCK_NUMBER_RPC, ()).await.unwrap(); + } } #[tokio::test] async fn test_first_provider_is_attempted() { let fallback_provider_builder = FallbackProviderBuilder::default(); let providers = vec![ - ProviderMock::new(), - ProviderMock::new(), - ProviderMock::new(), + EthereumProviderMock::default(), + EthereumProviderMock::default(), + EthereumProviderMock::default(), ]; let fallback_provider = fallback_provider_builder.add_providers(providers).build(); let ethereum_fallback_provider = EthereumFallbackProvider::new(fallback_provider); - ethereum_fallback_provider - .request::<_, u64>(BLOCK_NUMBER_RPC, ()) - .await - .unwrap(); - let provider_call_count: Vec<_> = get_call_counts(ðereum_fallback_provider).await; + ethereum_fallback_provider.low_level_test_call().await; + let provider_call_count: Vec<_> = + ProviderMock::get_call_counts(ðereum_fallback_provider).await; assert_eq!(provider_call_count, vec![1, 0, 0]); } @@ -233,21 +229,18 @@ mod tests { async fn test_one_stalled_provider() { let fallback_provider_builder = FallbackProviderBuilder::default(); let providers = vec![ - ProviderMock::new(), - ProviderMock::new(), - ProviderMock::new(), + EthereumProviderMock::new(Some(Duration::from_millis(10))), + EthereumProviderMock::default(), + EthereumProviderMock::default(), ]; let fallback_provider = fallback_provider_builder .add_providers(providers) .with_max_block_time(Duration::from_secs(0)) .build(); let ethereum_fallback_provider = EthereumFallbackProvider::new(fallback_provider); - ethereum_fallback_provider - .request::<_, u64>(BLOCK_NUMBER_RPC, ()) - .await - .unwrap(); - - let provider_call_count: Vec<_> = get_call_counts(ðereum_fallback_provider).await; + ethereum_fallback_provider.low_level_test_call().await; + let provider_call_count: Vec<_> = + ProviderMock::get_call_counts(ðereum_fallback_provider).await; assert_eq!(provider_call_count, vec![0, 0, 2]); } diff --git a/rust/chains/hyperlane-ethereum/src/trait_builder.rs b/rust/chains/hyperlane-ethereum/src/trait_builder.rs index 395433c1fa..7fed53ca55 100644 --- a/rust/chains/hyperlane-ethereum/src/trait_builder.rs +++ b/rust/chains/hyperlane-ethereum/src/trait_builder.rs @@ -15,8 +15,8 @@ use reqwest::{Client, Url}; use thiserror::Error; use ethers_prometheus::json_rpc_client::{ - JsonRpcClientMetrics, JsonRpcClientMetricsBuilder, NodeInfo, PrometheusJsonRpcClient, - PrometheusJsonRpcClientConfig, + JsonRpcBlockGetter, JsonRpcClientMetrics, JsonRpcClientMetricsBuilder, NodeInfo, + PrometheusJsonRpcClient, PrometheusJsonRpcClientConfig, }; use ethers_prometheus::middleware::{MiddlewareMetrics, PrometheusMiddlewareConf}; use hyperlane_core::{ @@ -112,7 +112,10 @@ pub trait BuildableWithProvider { builder = builder.add_provider(metrics_provider); } let fallback_provider = builder.build(); - let ethereum_fallback_provider = EthereumFallbackProvider::new(fallback_provider); + let ethereum_fallback_provider = EthereumFallbackProvider::< + _, + JsonRpcBlockGetter>, + >::new(fallback_provider); self.build(ethereum_fallback_provider, locator, signer) .await? } diff --git a/rust/chains/hyperlane-fuel/Cargo.toml b/rust/chains/hyperlane-fuel/Cargo.toml index 7dabcdd514..82bdbc782e 100644 --- a/rust/chains/hyperlane-fuel/Cargo.toml +++ b/rust/chains/hyperlane-fuel/Cargo.toml @@ -19,7 +19,7 @@ tracing-futures.workspace = true tracing.workspace = true url.workspace = true -hyperlane-core = { path = "../../hyperlane-core" } +hyperlane-core = { path = "../../hyperlane-core", features = ["fallback-provider"]} [build-dependencies] abigen = { path = "../../utils/abigen", features = ["fuels"] } diff --git a/rust/chains/hyperlane-sealevel/Cargo.toml b/rust/chains/hyperlane-sealevel/Cargo.toml index 248e3dfac1..ab4e9b17f6 100644 --- a/rust/chains/hyperlane-sealevel/Cargo.toml +++ b/rust/chains/hyperlane-sealevel/Cargo.toml @@ -24,7 +24,7 @@ tracing.workspace = true url.workspace = true account-utils = { path = "../../sealevel/libraries/account-utils" } -hyperlane-core = { path = "../../hyperlane-core", features = ["solana"] } +hyperlane-core = { path = "../../hyperlane-core", features = ["solana", "fallback-provider"] } hyperlane-sealevel-interchain-security-module-interface = { path = "../../sealevel/libraries/interchain-security-module-interface" } hyperlane-sealevel-mailbox = { path = "../../sealevel/programs/mailbox", features = ["no-entrypoint"] } hyperlane-sealevel-igp = { path = "../../sealevel/programs/hyperlane-sealevel-igp", features = ["no-entrypoint"] } diff --git a/rust/config/mainnet3_config.json b/rust/config/mainnet3_config.json index 6c91592fb7..1488cbec38 100644 --- a/rust/config/mainnet3_config.json +++ b/rust/config/mainnet3_config.json @@ -465,6 +465,46 @@ "from": 437300 } }, + "neutron": { + "name": "neutron", + "domainId": "1853125230", + "chainId": "neutron-1", + "mailbox": "0x848426d50eb2104d5c6381ec63757930b1c14659c40db8b8081e516e7c5238fc", + "interchainGasPaymaster": "0x504ee9ac43ec5814e00c7d21869a90ec52becb489636bdf893b7df9d606b5d67", + "validatorAnnounce": "0xf3aa0d652226e21ae35cd9035c492ae41725edc9036edf0d6a48701b153b90a0", + "merkleTreeHook": "0xcd30a0001cc1f436c41ef764a712ebabc5a144140e3fd03eafe64a9a24e4e27c", + "protocol": "cosmos", + "finalityBlocks": 1, + "rpcUrls": [ + { + "http": "https://rpc-kralum.neutron-1.neutron.org" + } + ], + "grpcUrls": [ + { + "http": "https://grpc-kralum.neutron-1.neutron.org:80" + } + ], + "canonicalAsset": "untrn", + "bech32Prefix": "neutron", + "gasPrice": { + "amount": "0.57", + "denom": "untrn" + }, + "contractAddressBytes": 32, + "index": { + "from": 4000000, + "chunk": 100000 + }, + "blocks": { + "reorgPeriod": 1 + }, + "signer": { + "type": "cosmosKey", + "key": "0x5486418967eabc770b0fcb995f7ef6d9a72f7fc195531ef76c5109f44f51af26", + "prefix": "neutron" + } + }, "injective": { "name": "injective", "domainId": "6909546", @@ -480,7 +520,11 @@ "http": "https://rpc-injective.goldenratiostaking.net:443" } ], - "grpcUrl": "https://injective-grpc.publicnode.com/", + "grpcUrls": [ + { + "http": "https://injective-grpc.goldenratiostaking.net:443" + } + ], "canonicalAsset": "inj", "bech32Prefix": "inj", "gasPrice": { @@ -860,4 +904,4 @@ } }, "defaultRpcConsensusType": "fallback" -} +} \ No newline at end of file diff --git a/rust/ethers-prometheus/src/json_rpc_client.rs b/rust/ethers-prometheus/src/json_rpc_client.rs index 7e0c4d1fee..2cc8defe9b 100644 --- a/rust/ethers-prometheus/src/json_rpc_client.rs +++ b/rust/ethers-prometheus/src/json_rpc_client.rs @@ -186,9 +186,11 @@ where } } -impl From> for Box { +impl From> + for JsonRpcBlockGetter> +{ fn from(val: PrometheusJsonRpcClient) -> Self { - Box::new(JsonRpcBlockGetter::new(val)) + JsonRpcBlockGetter::new(val) } } diff --git a/rust/helm/hyperlane-agent/templates/external-secret.yaml b/rust/helm/hyperlane-agent/templates/external-secret.yaml index 5d0eae5ced..a6c82953e3 100644 --- a/rust/helm/hyperlane-agent/templates/external-secret.yaml +++ b/rust/helm/hyperlane-agent/templates/external-secret.yaml @@ -29,7 +29,7 @@ spec: {{- if not .disabled }} HYP_CHAINS_{{ .name | upper }}_CUSTOMRPCURLS: {{ printf "'{{ .%s_rpcs | mustFromJson | join \",\" }}'" .name }} {{- if eq .protocol "cosmos" }} - HYP_CHAINS_{{ .name | upper }}_GRPCURL: {{ printf "'{{ .%s_grpc }}'" .name }} + HYP_CHAINS_{{ .name | upper }}_CUSTOMGRPCURLS: {{ printf "'{{ .%s_grpcs | mustFromJson | join \",\" }}'" .name }} {{- end }} {{- end }} {{- end }} @@ -44,9 +44,9 @@ spec: remoteRef: key: {{ printf "%s-rpc-endpoints-%s" $.Values.hyperlane.runEnv .name }} {{- if eq .protocol "cosmos" }} - - secretKey: {{ printf "%s_grpc" .name }} + - secretKey: {{ printf "%s_grpcs" .name }} remoteRef: - key: {{ printf "%s-grpc-endpoint-%s" $.Values.hyperlane.runEnv .name }} + key: {{ printf "%s-grpc-endpoints-%s" $.Values.hyperlane.runEnv .name }} {{- end }} {{- end }} {{- end }} diff --git a/rust/hyperlane-base/Cargo.toml b/rust/hyperlane-base/Cargo.toml index 4e78c30243..960fb2dad2 100644 --- a/rust/hyperlane-base/Cargo.toml +++ b/rust/hyperlane-base/Cargo.toml @@ -40,6 +40,7 @@ tracing-subscriber = { workspace = true, features = ["json", "ansi"] } tracing.workspace = true url.workspace = true warp.workspace = true +ya-gcp.workspace = true backtrace = { workspace = true, optional = true } backtrace-oneline = { path = "../utils/backtrace-oneline", optional = true } diff --git a/rust/hyperlane-base/src/settings/checkpoint_syncer.rs b/rust/hyperlane-base/src/settings/checkpoint_syncer.rs index 01640747eb..cb4fa1c8d6 100644 --- a/rust/hyperlane-base/src/settings/checkpoint_syncer.rs +++ b/rust/hyperlane-base/src/settings/checkpoint_syncer.rs @@ -1,11 +1,13 @@ +use crate::{ + CheckpointSyncer, GcsStorageClientBuilder, LocalStorage, S3Storage, GCS_SERVICE_ACCOUNT_KEY, + GCS_USER_SECRET, +}; use core::str::FromStr; -use std::path::PathBuf; - use eyre::{eyre, Context, Report, Result}; use prometheus::IntGauge; use rusoto_core::Region; - -use crate::{CheckpointSyncer, LocalStorage, S3Storage}; +use std::{env, path::PathBuf}; +use ya_gcp::{AuthFlow, ServiceAccountAuth}; /// Checkpoint Syncer types #[derive(Debug, Clone)] @@ -24,6 +26,18 @@ pub enum CheckpointSyncerConf { /// S3 Region region: Region, }, + /// A checkpoint syncer on Google Cloud Storage + Gcs { + /// Bucket name + bucket: String, + /// Folder name inside bucket - defaults to the root of the bucket + folder: Option, + /// A path to the oauth service account key json file. + service_account_key: Option, + /// Path to oauth user secrets, like those created by + /// `gcloud auth application-default login` + user_secrets: Option, + }, } impl FromStr for CheckpointSyncerConf { @@ -54,6 +68,28 @@ impl FromStr for CheckpointSyncerConf { "file" => Ok(CheckpointSyncerConf::LocalStorage { path: suffix.into(), }), + // for google cloud both options (with or without folder) from str are for anonymous access only + // or env variables parsing + "gs" => { + let service_account_key = env::var(GCS_SERVICE_ACCOUNT_KEY).ok(); + let user_secrets = env::var(GCS_USER_SECRET).ok(); + if let Some(ind) = suffix.find('/') { + let (bucket, folder) = suffix.split_at(ind); + Ok(Self::Gcs { + bucket: bucket.into(), + folder: Some(folder.into()), + service_account_key, + user_secrets, + }) + } else { + Ok(Self::Gcs { + bucket: suffix.into(), + folder: None, + service_account_key, + user_secrets, + }) + } + } _ => Err(eyre!("Unknown storage location prefix `{prefix}`")), } } @@ -61,7 +97,7 @@ impl FromStr for CheckpointSyncerConf { impl CheckpointSyncerConf { /// Turn conf info a Checkpoint Syncer - pub fn build( + pub async fn build( &self, latest_index_gauge: Option, ) -> Result, Report> { @@ -79,6 +115,27 @@ impl CheckpointSyncerConf { region.clone(), latest_index_gauge, )), + CheckpointSyncerConf::Gcs { + bucket, + folder, + service_account_key, + user_secrets, + } => { + let auth = if let Some(path) = service_account_key { + AuthFlow::ServiceAccount(ServiceAccountAuth::Path(path.into())) + } else if let Some(path) = user_secrets { + AuthFlow::UserAccount(path.into()) + } else { + // Public data access only - no `insert` + AuthFlow::NoAuth + }; + + Box::new( + GcsStorageClientBuilder::new(auth) + .build(bucket, folder.to_owned()) + .await?, + ) + } }) } } diff --git a/rust/hyperlane-base/src/settings/parser/connection_parser.rs b/rust/hyperlane-base/src/settings/parser/connection_parser.rs index 5d42ce4411..0d47d6eca9 100644 --- a/rust/hyperlane-base/src/settings/parser/connection_parser.rs +++ b/rust/hyperlane-base/src/settings/parser/connection_parser.rs @@ -6,7 +6,7 @@ use url::Url; use crate::settings::envs::*; use crate::settings::ChainConnectionConf; -use super::{parse_cosmos_gas_price, ValueParser}; +use super::{parse_base_and_override_urls, parse_cosmos_gas_price, ValueParser}; pub fn build_ethereum_connection_conf( rpcs: &[Url], @@ -43,19 +43,8 @@ pub fn build_cosmos_connection_conf( err: &mut ConfigParsingError, ) -> Option { let mut local_err = ConfigParsingError::default(); - - let grpc_url = chain - .chain(&mut local_err) - .get_key("grpcUrl") - .parse_string() - .end() - .or_else(|| { - local_err.push( - &chain.cwp + "grpc_url", - eyre!("Missing grpc definitions for chain"), - ); - None - }); + let grpcs = + parse_base_and_override_urls(chain, "grpcUrls", "customGrpcUrls", "http", &mut local_err); let chain_id = chain .chain(&mut local_err) @@ -114,7 +103,7 @@ pub fn build_cosmos_connection_conf( None } else { Some(ChainConnectionConf::Cosmos(h_cosmos::ConnectionConf::new( - grpc_url.unwrap().to_string(), + grpcs, rpcs.first().unwrap().to_string(), chain_id.unwrap().to_string(), prefix.unwrap().to_string(), diff --git a/rust/hyperlane-base/src/settings/parser/mod.rs b/rust/hyperlane-base/src/settings/parser/mod.rs index 76c88fe1b3..3bfb52f91f 100644 --- a/rust/hyperlane-base/src/settings/parser/mod.rs +++ b/rust/hyperlane-base/src/settings/parser/mod.rs @@ -18,6 +18,7 @@ use hyperlane_core::{ use itertools::Itertools; use serde::Deserialize; use serde_json::Value; +use url::Url; pub use self::json_value_parser::ValueParser; pub use super::envs::*; @@ -134,47 +135,7 @@ fn parse_chain( .parse_u32() .unwrap_or(1); - let rpcs_base = chain - .chain(&mut err) - .get_key("rpcUrls") - .into_array_iter() - .map(|urls| { - urls.filter_map(|v| { - v.chain(&mut err) - .get_key("http") - .parse_from_str("Invalid http url") - .end() - }) - .collect_vec() - }) - .unwrap_or_default(); - - let rpc_overrides = chain - .chain(&mut err) - .get_opt_key("customRpcUrls") - .parse_string() - .end() - .map(|urls| { - urls.split(',') - .filter_map(|url| { - url.parse() - .take_err(&mut err, || &chain.cwp + "customRpcUrls") - }) - .collect_vec() - }); - - let rpcs = rpc_overrides.unwrap_or(rpcs_base); - - if rpcs.is_empty() { - err.push( - &chain.cwp + "rpc_urls", - eyre!("Missing base rpc definitions for chain"), - ); - err.push( - &chain.cwp + "custom_rpc_urls", - eyre!("Also missing rpc overrides for chain"), - ); - } + let rpcs = parse_base_and_override_urls(&chain, "rpcUrls", "customRpcUrls", "http", &mut err); let from = chain .chain(&mut err) @@ -418,3 +379,66 @@ fn parse_cosmos_gas_price(gas_price: ValueParser) -> ConfigResult Vec { + chain + .chain(err) + .get_key(key) + .into_array_iter() + .map(|urls| { + urls.filter_map(|v| { + v.chain(err) + .get_key(protocol) + .parse_from_str("Invalid url") + .end() + }) + .collect_vec() + }) + .unwrap_or_default() +} + +fn parse_custom_urls( + chain: &ValueParser, + key: &str, + err: &mut ConfigParsingError, +) -> Option> { + chain + .chain(err) + .get_opt_key(key) + .parse_string() + .end() + .map(|urls| { + urls.split(',') + .filter_map(|url| url.parse().take_err(err, || &chain.cwp + "customGrpcUrls")) + .collect_vec() + }) +} + +fn parse_base_and_override_urls( + chain: &ValueParser, + base_key: &str, + override_key: &str, + protocol: &str, + err: &mut ConfigParsingError, +) -> Vec { + let base = parse_urls(chain, base_key, protocol, err); + let overrides = parse_custom_urls(chain, override_key, err); + let combined = overrides.unwrap_or(base); + + if combined.is_empty() { + err.push( + &chain.cwp + "rpc_urls", + eyre!("Missing base rpc definitions for chain"), + ); + err.push( + &chain.cwp + "custom_rpc_urls", + eyre!("Also missing rpc overrides for chain"), + ); + } + combined +} diff --git a/rust/hyperlane-base/src/types/gcs_storage.rs b/rust/hyperlane-base/src/types/gcs_storage.rs new file mode 100644 index 0000000000..ebd1589a4f --- /dev/null +++ b/rust/hyperlane-base/src/types/gcs_storage.rs @@ -0,0 +1,204 @@ +use crate::CheckpointSyncer; +use async_trait::async_trait; +use derive_new::new; +use eyre::{bail, Result}; +use hyperlane_core::{SignedAnnouncement, SignedCheckpointWithMessageId}; +use std::fmt; +use ya_gcp::{storage::StorageClient, AuthFlow, ClientBuilder, ClientBuilderConfig}; + +const LATEST_INDEX_KEY: &str = "gcsLatestIndexKey"; +const ANNOUNCEMENT_KEY: &str = "gcsAnnouncementKey"; +/// Path to GCS users_secret file +pub const GCS_USER_SECRET: &str = "GCS_USER_SECRET"; +/// Path to GCS Service account key +pub const GCS_SERVICE_ACCOUNT_KEY: &str = "GCS_SERVICE_ACCOUNT_KEY"; + +/// Google Cloud Storage client builder +/// Provide `AuthFlow::NoAuth` for no-auth access to public bucket +/// # Example 1 - anonymous client with access to public bucket +/// ``` +/// use hyperlane_base::GcsStorageClientBuilder; +/// use ya_gcp::AuthFlow; +/// # #[tokio::main] +/// # async fn main() { +/// let client = GcsStorageClientBuilder::new(AuthFlow::NoAuth) +/// .build("HyperlaneBucket", None) +/// .await.expect("failed to instantiate anonymous client"); +/// # } +///``` +/// +/// For authenticated write access to bucket proper file path must be provided. +/// # WARN: panic-s if file path is incorrect or data in it as faulty +/// +/// # Example 2 - service account key +/// ```should_panic +/// use hyperlane_base::GcsStorageClientBuilder; +/// use ya_gcp::{AuthFlow, ServiceAccountAuth}; +/// # #[tokio::main] +/// # async fn main() { +/// let auth = +/// AuthFlow::ServiceAccount(ServiceAccountAuth::Path("path/to/sac.json".into())); +/// +/// let client = GcsStorageClientBuilder::new(auth) +/// .build("HyperlaneBucket", None) +/// .await.expect("failed to instantiate anonymous client"); +/// # } +///``` +/// # Example 3 - user secret access +/// ```should_panic +/// use hyperlane_base::GcsStorageClientBuilder; +/// use ya_gcp::AuthFlow; +/// # #[tokio::main] +/// # async fn main() { +/// let auth = +/// AuthFlow::UserAccount("path/to/user_secret.json".into()); +/// +/// let client = GcsStorageClientBuilder::new(auth) +/// .build("HyperlaneBucket", None) +/// .await.expect("failed to instantiate anonymous client"); +/// # } +///``` +#[derive(Debug, new)] +pub struct GcsStorageClientBuilder { + auth: AuthFlow, +} + +/// Google Cloud Storage client +/// Enables use of any of service account key OR user secrets to authenticate +/// For anonymous access to public data provide `(None, None)` to Builder +pub struct GcsStorageClient { + // GCS storage client + // # Details: + inner: StorageClient, + // bucket name of this client's storage + bucket: String, +} + +impl GcsStorageClientBuilder { + /// Instantiates `ya_gcp:StorageClient` based on provided auth method + /// # Param + /// * `baucket_name` - String name of target bucket to work with, will be used by all store and get ops + pub async fn build( + self, + bucket_name: impl Into, + folder: Option, + ) -> Result { + let inner = ClientBuilder::new(ClientBuilderConfig::new().auth_flow(self.auth)) + .await? + .build_storage_client(); + let bucket = if let Some(folder) = folder { + format! {"{}/{}", bucket_name.into(), folder} + } else { + bucket_name.into() + }; + + Ok(GcsStorageClient { inner, bucket }) + } +} + +impl GcsStorageClient { + // convinience formatter + fn get_checkpoint_key(index: u32) -> String { + format!("checkpoint_{index}_with_id.json") + } + // #test only method[s] + #[cfg(test)] + pub(crate) async fn get_by_path(&self, path: impl AsRef) -> Result<()> { + self.inner.get_object(&self.bucket, path).await?; + Ok(()) + } +} + +// required by `CheckpointSyncer` +impl fmt::Debug for GcsStorageClient { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("S3Storage") + .field("bucket", &self.bucket) + .finish() + } +} + +#[async_trait] +impl CheckpointSyncer for GcsStorageClient { + /// Read the highest index of this Syncer + async fn latest_index(&self) -> Result> { + match self.inner.get_object(&self.bucket, LATEST_INDEX_KEY).await { + Ok(data) => Ok(Some(serde_json::from_slice(data.as_ref())?)), + Err(e) => match e { + // never written before to this bucket + ya_gcp::storage::ObjectError::InvalidName(_) => Ok(None), + _ => bail!(e), + }, + } + } + + /// Writes the highest index of this Syncer + async fn write_latest_index(&self, index: u32) -> Result<()> { + let d = serde_json::to_vec(&index)?; + self.inner + .insert_object(&self.bucket, LATEST_INDEX_KEY, d) + .await?; + Ok(()) + } + + /// Update the latest index of this syncer if necessary + async fn update_latest_index(&self, index: u32) -> Result<()> { + let curr = self.latest_index().await?.unwrap_or(0); + if index > curr { + self.write_latest_index(index).await?; + } + Ok(()) + } + + /// Attempt to fetch the signed (checkpoint, messageId) tuple at this index + async fn fetch_checkpoint(&self, index: u32) -> Result> { + let res = self + .inner + .get_object(&self.bucket, GcsStorageClient::get_checkpoint_key(index)) + .await?; + Ok(Some(serde_json::from_slice(res.as_ref())?)) + } + + /// Write the signed (checkpoint, messageId) tuple to this syncer + async fn write_checkpoint( + &self, + signed_checkpoint: &SignedCheckpointWithMessageId, + ) -> Result<()> { + self.inner + .insert_object( + &self.bucket, + GcsStorageClient::get_checkpoint_key(signed_checkpoint.value.index), + serde_json::to_vec(signed_checkpoint)?, + ) + .await?; + Ok(()) + } + + /// Write the signed announcement to this syncer + async fn write_announcement(&self, signed_announcement: &SignedAnnouncement) -> Result<()> { + self.inner + .insert_object( + &self.bucket, + ANNOUNCEMENT_KEY, + serde_json::to_string(signed_announcement)?, + ) + .await?; + Ok(()) + } + + /// Return the announcement storage location for this syncer + fn announcement_location(&self) -> String { + format!("gs://{}/{}", &self.bucket, ANNOUNCEMENT_KEY) + } +} + +#[tokio::test] +async fn public_landset_no_auth_works_test() { + const LANDSAT_BUCKET: &str = "gcp-public-data-landsat"; + const LANDSAT_KEY: &str = "LC08/01/001/003/LC08_L1GT_001003_20140812_20170420_01_T2/LC08_L1GT_001003_20140812_20170420_01_T2_B3.TIF"; + let client = GcsStorageClientBuilder::new(AuthFlow::NoAuth) + .build(LANDSAT_BUCKET, None) + .await + .unwrap(); + assert!(client.get_by_path(LANDSAT_KEY).await.is_ok()); +} diff --git a/rust/hyperlane-base/src/types/mod.rs b/rust/hyperlane-base/src/types/mod.rs index f1e23c773c..bd07fe23c9 100644 --- a/rust/hyperlane-base/src/types/mod.rs +++ b/rust/hyperlane-base/src/types/mod.rs @@ -1,7 +1,9 @@ +mod gcs_storage; mod local_storage; mod multisig; mod s3_storage; +pub use gcs_storage::*; pub use local_storage::*; pub use multisig::*; pub use s3_storage::*; diff --git a/rust/hyperlane-core/Cargo.toml b/rust/hyperlane-core/Cargo.toml index 8329bce5f2..40468bef6c 100644 --- a/rust/hyperlane-core/Cargo.toml +++ b/rust/hyperlane-core/Cargo.toml @@ -37,6 +37,7 @@ serde_json = { workspace = true } sha3 = { workspace = true } strum = { workspace = true, optional = true, features = ["derive"] } thiserror = { workspace = true } +tokio = { workspace = true, optional = true, features = ["rt", "time"] } tracing.workspace = true primitive-types = { workspace = true, optional = true } solana-sdk = { workspace = true, optional = true } @@ -54,3 +55,4 @@ agent = ["ethers", "strum"] strum = ["dep:strum"] ethers = ["dep:ethers-core", "dep:ethers-contract", "dep:ethers-providers", "dep:primitive-types"] solana = ["dep:solana-sdk"] +fallback-provider = ["tokio"] diff --git a/rust/hyperlane-core/src/rpc_clients/fallback.rs b/rust/hyperlane-core/src/rpc_clients/fallback.rs index 6adcb76f55..6f75cbc4c8 100644 --- a/rust/hyperlane-core/src/rpc_clients/fallback.rs +++ b/rust/hyperlane-core/src/rpc_clients/fallback.rs @@ -1,15 +1,22 @@ use async_rwlock::RwLock; use async_trait::async_trait; use derive_new::new; +use itertools::Itertools; use std::{ - fmt::Debug, + fmt::{Debug, Formatter}, + future::Future, + marker::PhantomData, + pin::Pin, sync::Arc, time::{Duration, Instant}, }; -use tracing::info; +use tokio; +use tracing::{info, trace, warn_span}; use crate::ChainCommunicationError; +use super::RpcClientError; + /// Read the current block number from a chain. #[async_trait] pub trait BlockNumberGetter: Send + Sync + Debug { @@ -38,7 +45,6 @@ impl PrioritizedProviderInner { } } } - /// Sub-providers and priority information pub struct PrioritizedProviders { /// Unsorted list of providers this provider calls @@ -49,28 +55,56 @@ pub struct PrioritizedProviders { /// A provider that bundles multiple providers and attempts to call the first, /// then the second, and so on until a response is received. -pub struct FallbackProvider { +/// +/// Although no trait bounds are used in the struct definition, the intended purpose of `B` +/// is to be bound by `BlockNumberGetter` and have `T` be convertible to `B`. That is, +/// inner providers should be able to get the current block number, or be convertible into +/// something that is. +pub struct FallbackProvider { /// The sub-providers called by this provider pub inner: Arc>, max_block_time: Duration, + _phantom: PhantomData, } -impl Clone for FallbackProvider { +impl Clone for FallbackProvider { fn clone(&self) -> Self { Self { inner: self.inner.clone(), max_block_time: self.max_block_time, + _phantom: PhantomData, } } } -impl FallbackProvider +impl Debug for FallbackProvider +where + T: Debug, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + // iterate the inner providers and write them to the formatter + f.debug_struct("FallbackProvider") + .field( + "providers", + &self + .inner + .providers + .iter() + .map(|v| format!("{:?}", v)) + .join(", "), + ) + .finish() + } +} + +impl FallbackProvider where - T: Into> + Debug + Clone, + T: Into + Debug + Clone, + B: BlockNumberGetter, { /// Convenience method for creating a `FallbackProviderBuilder` with same /// `JsonRpcClient` types - pub fn builder() -> FallbackProviderBuilder { + pub fn builder() -> FallbackProviderBuilder { FallbackProviderBuilder::default() } @@ -112,7 +146,7 @@ where return; } - let block_getter: Box = provider.clone().into(); + let block_getter: B = provider.clone().into(); let current_block_height = block_getter .get_block_number() .await @@ -130,25 +164,62 @@ where .await; } } + + /// Call the first provider, then the second, and so on (in order of priority) until a response is received. + /// If all providers fail, return an error. + pub async fn call( + &self, + mut f: impl FnMut(T) -> Pin> + Send>>, + ) -> Result { + let mut errors = vec![]; + // make sure we do at least 4 total retries. + while errors.len() <= 3 { + if !errors.is_empty() { + tokio::time::sleep(Duration::from_millis(100)).await; + } + let priorities_snapshot = self.take_priorities_snapshot().await; + for (idx, priority) in priorities_snapshot.iter().enumerate() { + let provider = &self.inner.providers[priority.index]; + let resp = f(provider.clone()).await; + self.handle_stalled_provider(priority, provider).await; + let _span = + warn_span!("FallbackProvider::call", fallback_count=%idx, provider_index=%priority.index, ?provider).entered(); + match resp { + Ok(v) => return Ok(v), + Err(e) => { + trace!( + error=?e, + "Got error from inner fallback provider", + ); + errors.push(e) + } + } + } + } + + Err(RpcClientError::FallbackProvidersFailed(errors).into()) + } } /// Builder to create a new fallback provider. #[derive(Debug, Clone)] -pub struct FallbackProviderBuilder { +pub struct FallbackProviderBuilder { providers: Vec, max_block_time: Duration, + _phantom: PhantomData, } -impl Default for FallbackProviderBuilder { +impl Default for FallbackProviderBuilder { fn default() -> Self { Self { providers: Vec::new(), max_block_time: MAX_BLOCK_TIME, + _phantom: PhantomData, } } } -impl FallbackProviderBuilder { +impl FallbackProviderBuilder { /// Add a new provider to the set. Each new provider will be a lower /// priority than the previous. pub fn add_provider(mut self, provider: T) -> Self { @@ -170,7 +241,7 @@ impl FallbackProviderBuilder { } /// Create a fallback provider. - pub fn build(self) -> FallbackProvider { + pub fn build(self) -> FallbackProvider { let provider_count = self.providers.len(); let prioritized_providers = PrioritizedProviders { providers: self.providers, @@ -184,6 +255,80 @@ impl FallbackProviderBuilder { FallbackProvider { inner: Arc::new(prioritized_providers), max_block_time: self.max_block_time, + _phantom: PhantomData, + } + } +} + +/// Utilities to import when testing chain-specific fallback providers +pub mod test { + use super::*; + use std::{ + ops::Deref, + sync::{Arc, Mutex}, + }; + + /// Provider that stores requests and optionally sleeps before returning a dummy value + #[derive(Debug, Clone)] + pub struct ProviderMock { + // Store requests as tuples of (method, params) + // Even if the tests were single-threaded, need the arc-mutex + // for interior mutability in `JsonRpcClient::request` + requests: Arc>>, + request_sleep: Option, + } + + impl Default for ProviderMock { + fn default() -> Self { + Self { + requests: Arc::new(Mutex::new(vec![])), + request_sleep: None, + } + } + } + + impl ProviderMock { + /// Create a new provider + pub fn new(request_sleep: Option) -> Self { + Self { + request_sleep, + ..Default::default() + } + } + + /// Push a request to the internal store for later inspection + pub fn push(&self, method: &str, params: T) { + self.requests + .lock() + .unwrap() + .push((method.to_owned(), format!("{:?}", params))); + } + + /// Get the stored requests + pub fn requests(&self) -> Vec<(String, String)> { + self.requests.lock().unwrap().clone() + } + + /// Set the sleep duration + pub fn request_sleep(&self) -> Option { + self.request_sleep + } + + /// Get how many times each provider was called + pub async fn get_call_counts, B>( + fallback_provider: &FallbackProvider, + ) -> Vec { + fallback_provider + .inner + .priorities + .read() + .await + .iter() + .map(|p| { + let provider = &fallback_provider.inner.providers[p.index]; + provider.requests().len() + }) + .collect() } } } diff --git a/rust/hyperlane-core/src/rpc_clients/mod.rs b/rust/hyperlane-core/src/rpc_clients/mod.rs index 78851f9f26..02aaae99f5 100644 --- a/rust/hyperlane-core/src/rpc_clients/mod.rs +++ b/rust/hyperlane-core/src/rpc_clients/mod.rs @@ -1,4 +1,8 @@ -pub use self::{error::*, fallback::*}; +pub use self::error::*; + +#[cfg(feature = "fallback-provider")] +pub use self::fallback::*; mod error; +#[cfg(feature = "fallback-provider")] mod fallback; diff --git a/rust/sealevel/environments/local-e2e/warp-routes/testwarproute/program-ids.json b/rust/sealevel/environments/local-e2e/warp-routes/testwarproute/program-ids.json index ba62748efe..c5e945eae3 100644 --- a/rust/sealevel/environments/local-e2e/warp-routes/testwarproute/program-ids.json +++ b/rust/sealevel/environments/local-e2e/warp-routes/testwarproute/program-ids.json @@ -1,10 +1,10 @@ { - "sealeveltest1": { - "hex": "0xa77b4e2ed231894cc8cb8eee21adcc705d8489bccc6b2fcf40a358de23e60b7b", - "base58": "CGn8yNtSD3aTTqJfYhUb6s1aVTN75NzwtsFKo1e83aga" - }, "sealeveltest2": { "hex": "0x2317f9615d4ebc2419ad4b88580e2a80a03b2c7a60bc960de7d6934dbc37a87e", "base58": "3MzUPjP5LEkiHH82nEAe28Xtz9ztuMqWc8UmuKxrpVQH" + }, + "sealeveltest1": { + "hex": "0xa77b4e2ed231894cc8cb8eee21adcc705d8489bccc6b2fcf40a358de23e60b7b", + "base58": "CGn8yNtSD3aTTqJfYhUb6s1aVTN75NzwtsFKo1e83aga" } } \ No newline at end of file diff --git a/rust/utils/abigen/src/lib.rs b/rust/utils/abigen/src/lib.rs index 2e8d5ca081..b4b7970fdc 100644 --- a/rust/utils/abigen/src/lib.rs +++ b/rust/utils/abigen/src/lib.rs @@ -1,3 +1,4 @@ +#[cfg(feature = "fuels")] use fuels_code_gen::ProgramType; use std::collections::BTreeSet; use std::ffi::OsStr; diff --git a/rust/utils/run-locally/Cargo.toml b/rust/utils/run-locally/Cargo.toml index 03d771734e..bf57138f4e 100644 --- a/rust/utils/run-locally/Cargo.toml +++ b/rust/utils/run-locally/Cargo.toml @@ -29,7 +29,7 @@ ureq = { workspace = true, default-features = false } which.workspace = true macro_rules_attribute.workspace = true regex.workspace = true -hpl-interface.workspace = true +hyperlane-cosmwasm-interface.workspace = true cosmwasm-schema.workspace = true [features] diff --git a/rust/utils/run-locally/src/cosmos/crypto.rs b/rust/utils/run-locally/src/cosmos/crypto.rs index 9b336f4df7..75924df69a 100644 --- a/rust/utils/run-locally/src/cosmos/crypto.rs +++ b/rust/utils/run-locally/src/cosmos/crypto.rs @@ -24,7 +24,7 @@ pub fn pub_to_addr(pub_key: &[u8], prefix: &str) -> String { let sha_hash = sha256_digest(pub_key); let rip_hash = ripemd160_digest(sha_hash); - let addr = hpl_interface::types::bech32_encode(prefix, &rip_hash).unwrap(); + let addr = hyperlane_cosmwasm_interface::types::bech32_encode(prefix, &rip_hash).unwrap(); addr.to_string() } diff --git a/rust/utils/run-locally/src/cosmos/deploy.rs b/rust/utils/run-locally/src/cosmos/deploy.rs index ab02a2bee4..4da016d865 100644 --- a/rust/utils/run-locally/src/cosmos/deploy.rs +++ b/rust/utils/run-locally/src/cosmos/deploy.rs @@ -1,5 +1,5 @@ use cosmwasm_schema::cw_serde; -use hpl_interface::{core, hook, igp, ism}; +use hyperlane_cosmwasm_interface::{core, hook, igp, ism}; use macro_rules_attribute::apply; use crate::utils::as_task; @@ -9,19 +9,18 @@ use super::{ types::{Codes, Deployments}, }; -#[cw_serde] -pub struct IsmMultisigInstantiateMsg { - pub owner: String, -} - #[cw_serde] pub struct TestMockMsgReceiverInstantiateMsg { pub hrp: String, } #[cw_serde] -pub struct IGPOracleInstantiateMsg { +struct IgpInstantiateMsg { + pub hrp: String, pub owner: String, + pub gas_token: String, + pub beneficiary: String, + pub default_gas_usage: String, // u128 doesnt work with cw_serde } #[cw_serde] @@ -52,22 +51,12 @@ pub fn deploy_cw_hyperlane( "hpl_mailbox", ); - // deploy igp set - #[cw_serde] - pub struct GasOracleInitMsg { - pub hrp: String, - pub owner: String, - pub gas_token: String, - pub beneficiary: String, - pub default_gas_usage: String, - } - let igp = cli.wasm_init( &endpoint, &deployer, Some(deployer_addr), codes.hpl_igp, - GasOracleInitMsg { + IgpInstantiateMsg { hrp: BECH32_PREFIX.to_string(), owner: deployer_addr.clone(), gas_token: "uosmo".to_string(), @@ -107,12 +96,25 @@ pub fn deploy_cw_hyperlane( &deployer, Some(deployer_addr), codes.hpl_ism_multisig, - IsmMultisigInstantiateMsg { + ism::multisig::InstantiateMsg { owner: deployer_addr.clone(), }, "hpl_ism_multisig", ); + // deploy pausable ism + let ism_pausable = cli.wasm_init( + &endpoint, + &deployer, + Some(deployer_addr), + codes.hpl_ism_pausable, + ism::pausable::InstantiateMsg { + owner: deployer_addr.clone(), + paused: false, + }, + "hpl_ism_pausable", + ); + // deploy ism - aggregation let ism_aggregate = cli.wasm_init( &endpoint, @@ -121,8 +123,8 @@ pub fn deploy_cw_hyperlane( codes.hpl_ism_aggregate, ism::aggregate::InstantiateMsg { owner: deployer_addr.clone(), - threshold: 1, - isms: vec![ism_multisig.clone()], + threshold: 2, + isms: vec![ism_multisig.clone(), ism_pausable.clone()], }, "hpl_ism_aggregate", ); @@ -134,7 +136,6 @@ pub fn deploy_cw_hyperlane( Some(deployer_addr), codes.hpl_hook_merkle, hook::merkle::InstantiateMsg { - owner: deployer_addr.clone(), mailbox: mailbox.to_string(), }, "hpl_hook_merkle", diff --git a/rust/utils/run-locally/src/cosmos/link.rs b/rust/utils/run-locally/src/cosmos/link.rs index ff3de059fa..ebf53bd36c 100644 --- a/rust/utils/run-locally/src/cosmos/link.rs +++ b/rust/utils/run-locally/src/cosmos/link.rs @@ -1,7 +1,7 @@ use std::path::Path; use cosmwasm_schema::cw_serde; -use hpl_interface::{ +use hyperlane_cosmwasm_interface::{ core, ism::{self}, }; @@ -69,18 +69,14 @@ pub struct MockQuoteDispatch { #[cw_serde] pub struct GeneralIsmValidatorMessage { - pub enroll_validator: EnrollValidatorMsg, + pub set_validators: SetValidatorsMsg, } #[cw_serde] -pub struct EnrollValidatorMsg { - pub set: EnrollValidatorSet, -} - -#[cw_serde] -pub struct EnrollValidatorSet { +pub struct SetValidatorsMsg { pub domain: u32, - pub validator: String, + pub threshold: u8, + pub validators: Vec, } fn link_network( @@ -105,7 +101,7 @@ fn link_network( let public_key = validator.priv_key.verifying_key().to_encoded_point(false); let public_key = public_key.as_bytes(); - let hash = hpl_interface::types::keccak256_hash(&public_key[1..]); + let hash = hyperlane_cosmwasm_interface::types::keccak256_hash(&public_key[1..]); let mut bytes = [0u8; 20]; bytes.copy_from_slice(&hash.as_slice()[12..]); @@ -115,24 +111,10 @@ fn link_network( linker, &network.deployments.ism_multisig, GeneralIsmValidatorMessage { - enroll_validator: EnrollValidatorMsg { - set: EnrollValidatorSet { - domain: target_domain, - validator: hex::encode(bytes).to_string(), - }, - }, - }, - vec![], - ); - - cli.wasm_execute( - &network.launch_resp.endpoint, - linker, - &network.deployments.ism_multisig, - ism::multisig::ExecuteMsg::SetThreshold { - set: ism::multisig::ThresholdSet { - domain: target_domain, + set_validators: SetValidatorsMsg { threshold: 1, + domain: target_domain, + validators: vec![hex::encode(bytes).to_string()], }, }, vec![], diff --git a/rust/utils/run-locally/src/cosmos/mod.rs b/rust/utils/run-locally/src/cosmos/mod.rs index a61df94166..b1b2b4dd47 100644 --- a/rust/utils/run-locally/src/cosmos/mod.rs +++ b/rust/utils/run-locally/src/cosmos/mod.rs @@ -5,8 +5,8 @@ use std::time::{Duration, Instant}; use std::{env, fs}; use cosmwasm_schema::cw_serde; -use hpl_interface::types::bech32_decode; use hyperlane_cosmos::RawCosmosAmount; +use hyperlane_cosmwasm_interface::types::bech32_decode; use macro_rules_attribute::apply; use maplit::hashmap; use tempfile::tempdir; @@ -57,8 +57,8 @@ fn default_keys<'a>() -> [(&'a str, &'a str); 6] { ] } -const CW_HYPERLANE_GIT: &str = "https://github.com/many-things/cw-hyperlane"; -const CW_HYPERLANE_VERSION: &str = "0.0.6-rc6"; +const CW_HYPERLANE_GIT: &str = "https://github.com/hyperlane-xyz/cosmwasm"; +const CW_HYPERLANE_VERSION: &str = "v0.0.6"; fn make_target() -> String { let os = if cfg!(target_os = "linux") { @@ -101,19 +101,22 @@ pub fn install_codes(dir: Option, local: bool) -> BTreeMap path map fs::read_dir(dir_path) diff --git a/rust/utils/run-locally/src/cosmos/types.rs b/rust/utils/run-locally/src/cosmos/types.rs index 795d12ff39..120ed05afd 100644 --- a/rust/utils/run-locally/src/cosmos/types.rs +++ b/rust/utils/run-locally/src/cosmos/types.rs @@ -1,7 +1,7 @@ use std::{collections::BTreeMap, path::PathBuf}; -use hpl_interface::types::bech32_decode; use hyperlane_cosmos::RawCosmosAmount; +use hyperlane_cosmwasm_interface::types::bech32_decode; use super::{cli::OsmosisCLI, CosmosNetwork}; @@ -44,6 +44,7 @@ pub struct Codes { pub hpl_igp_oracle: u64, pub hpl_ism_aggregate: u64, pub hpl_ism_multisig: u64, + pub hpl_ism_pausable: u64, pub hpl_ism_routing: u64, pub hpl_test_mock_ism: u64, pub hpl_test_mock_hook: u64, @@ -118,7 +119,7 @@ pub struct AgentConfig { pub protocol: String, pub chain_id: String, pub rpc_urls: Vec, - pub grpc_url: String, + pub grpc_urls: Vec, pub bech32_prefix: String, pub signer: AgentConfigSigner, pub index: AgentConfigIndex, @@ -156,7 +157,15 @@ impl AgentConfig { network.launch_resp.endpoint.rpc_addr.replace("tcp://", "") ), }], - grpc_url: format!("http://{}", network.launch_resp.endpoint.grpc_addr), + grpc_urls: vec![ + // The first url points to a nonexistent node, but is used for checking fallback provider logic + AgentUrl { + http: "localhost:1337".to_string(), + }, + AgentUrl { + http: format!("http://{}", network.launch_resp.endpoint.grpc_addr), + }, + ], bech32_prefix: "osmo".to_string(), signer: AgentConfigSigner { typ: "cosmosKey".to_string(), diff --git a/solidity/CHANGELOG.md b/solidity/CHANGELOG.md index cf7b6a84e6..fa903faa7c 100644 --- a/solidity/CHANGELOG.md +++ b/solidity/CHANGELOG.md @@ -1,5 +1,11 @@ # @hyperlane-xyz/core +## 3.6.2 + +### Patch Changes + +- @hyperlane-xyz/utils@3.6.2 + ## 3.6.1 ### Patch Changes diff --git a/solidity/package.json b/solidity/package.json index 102273e354..f3a6bb2b2d 100644 --- a/solidity/package.json +++ b/solidity/package.json @@ -1,10 +1,10 @@ { "name": "@hyperlane-xyz/core", "description": "Core solidity contracts for Hyperlane", - "version": "3.6.1", + "version": "3.6.2", "dependencies": { "@eth-optimism/contracts": "^0.6.0", - "@hyperlane-xyz/utils": "3.6.1", + "@hyperlane-xyz/utils": "3.6.2", "@openzeppelin/contracts": "^4.9.3", "@openzeppelin/contracts-upgradeable": "^v4.9.3" }, diff --git a/typescript/cli/CHANGELOG.md b/typescript/cli/CHANGELOG.md index d85ed3175d..ec9579f252 100644 --- a/typescript/cli/CHANGELOG.md +++ b/typescript/cli/CHANGELOG.md @@ -1,5 +1,13 @@ # @hyperlane-xyz/cli +## 3.6.2 + +### Patch Changes + +- 99fe93a5b: Removed IGP from preset hook config + - @hyperlane-xyz/sdk@3.6.2 + - @hyperlane-xyz/utils@3.6.2 + ## 3.6.1 ### Patch Changes diff --git a/typescript/cli/package.json b/typescript/cli/package.json index e2b5c9bda0..3e366d04c8 100644 --- a/typescript/cli/package.json +++ b/typescript/cli/package.json @@ -1,10 +1,10 @@ { "name": "@hyperlane-xyz/cli", - "version": "3.6.1", + "version": "3.6.2", "description": "A command-line utility for common Hyperlane operations", "dependencies": { - "@hyperlane-xyz/sdk": "3.6.1", - "@hyperlane-xyz/utils": "3.6.1", + "@hyperlane-xyz/sdk": "3.6.2", + "@hyperlane-xyz/utils": "3.6.2", "@inquirer/prompts": "^3.0.0", "bignumber.js": "^9.1.1", "chalk": "^5.3.0", diff --git a/typescript/cli/src/config/hooks.ts b/typescript/cli/src/config/hooks.ts index 6392d60f95..0c0f49111c 100644 --- a/typescript/cli/src/config/hooks.ts +++ b/typescript/cli/src/config/hooks.ts @@ -9,10 +9,7 @@ import { GasOracleContractType, HookType, HooksConfig, - MultisigConfig, chainMetadata, - defaultMultisigConfigs, - multisigIsmVerificationCost, } from '@hyperlane-xyz/sdk'; import { Address, @@ -83,41 +80,7 @@ export function isValidHookConfigMap(config: any) { return HooksConfigMapSchema.safeParse(config).success; } -export function presetHookConfigs( - owner: Address, - local: ChainName, - destinationChains: ChainName[], - multisigConfig?: MultisigConfig, -): HooksConfig { - const gasOracleType = destinationChains.reduce< - ChainMap - >((acc, chain) => { - acc[chain] = GasOracleContractType.StorageGasOracle; - return acc; - }, {}); - const overhead = destinationChains.reduce>((acc, chain) => { - let validatorThreshold: number; - let validatorCount: number; - if (multisigConfig) { - validatorThreshold = multisigConfig.threshold; - validatorCount = multisigConfig.validators.length; - } else if (local in defaultMultisigConfigs) { - validatorThreshold = defaultMultisigConfigs[local].threshold; - validatorCount = defaultMultisigConfigs[local].validators.length; - } else { - // default values - // fix here: https://github.com/hyperlane-xyz/issues/issues/773 - validatorThreshold = 2; - validatorCount = 3; - } - acc[chain] = multisigIsmVerificationCost( - validatorThreshold, - validatorCount, - ); - return acc; - }, {}); - - // TODO improve types here to avoid need for `as` casts +export function presetHookConfigs(owner: Address): HooksConfig { return { required: { type: HookType.PROTOCOL_FEE, @@ -127,20 +90,7 @@ export function presetHookConfigs( owner: owner, }, default: { - type: HookType.AGGREGATION, - hooks: [ - { - type: HookType.MERKLE_TREE, - }, - { - type: HookType.INTERCHAIN_GAS_PAYMASTER, - owner: owner, - beneficiary: owner, - gasOracleType, - overhead, - oracleKey: owner, - }, - ], + type: HookType.MERKLE_TREE, }, }; } diff --git a/typescript/cli/src/deploy/agent.ts b/typescript/cli/src/deploy/agent.ts index 0680eaf0bc..a3d66865d3 100644 --- a/typescript/cli/src/deploy/agent.ts +++ b/typescript/cli/src/deploy/agent.ts @@ -69,6 +69,7 @@ export async function runKurtosisAgentDeploy({ const kurtosisCloudLink = terminalLink( 'Cmd+Click or Ctrl+Click here', kurtosisCloudUrl, + { fallback: () => kurtosisCloudUrl }, ); logGreen( diff --git a/typescript/cli/src/deploy/core.ts b/typescript/cli/src/deploy/core.ts index 5f9537e132..14617f9b83 100644 --- a/typescript/cli/src/deploy/core.ts +++ b/typescript/cli/src/deploy/core.ts @@ -328,7 +328,6 @@ async function executeDeploy({ chains, defaultIsms, hooksConfig, - multisigConfigs, ); const coreContracts = await coreDeployer.deploy(coreConfigs); @@ -391,17 +390,9 @@ function buildCoreConfigMap( chains: ChainName[], defaultIsms: ChainMap, hooksConfig: ChainMap, - multisigConfigs: ChainMap, ): ChainMap { return chains.reduce>((config, chain) => { - const hooks = - hooksConfig[chain] ?? - presetHookConfigs( - owner, - chain, - chains.filter((c) => c !== chain), - multisigConfigs[chain], // if no multisig config, uses default 2/3 - ); + const hooks = hooksConfig[chain] ?? presetHookConfigs(owner); config[chain] = { owner, defaultIsm: defaultIsms[chain], @@ -481,18 +472,20 @@ async function writeAgentConfig( multiProvider: MultiProvider, ) { const startBlocks: ChainMap = {}; + const core = HyperlaneCore.fromAddressesMap(artifacts, multiProvider); + for (const chain of chains) { - const core = HyperlaneCore.fromAddressesMap(artifacts, multiProvider); const mailbox = core.getContracts(chain).mailbox; startBlocks[chain] = (await mailbox.deployedBlock()).toNumber(); } + const mergedAddressesMap = objMerge( sdkContractAddressesMap, artifacts, ) as ChainMap; const agentConfig = buildAgentConfig( - Object.keys(mergedAddressesMap), + chains, // Use only the chains that were deployed to multiProvider, mergedAddressesMap, startBlocks, diff --git a/typescript/cli/src/version.ts b/typescript/cli/src/version.ts index 86c2700664..658ea336e5 100644 --- a/typescript/cli/src/version.ts +++ b/typescript/cli/src/version.ts @@ -1 +1 @@ -export const VERSION = '3.6.1'; +export const VERSION = '3.6.2'; diff --git a/typescript/helloworld/CHANGELOG.md b/typescript/helloworld/CHANGELOG.md index 992eedca50..40cff8da4b 100644 --- a/typescript/helloworld/CHANGELOG.md +++ b/typescript/helloworld/CHANGELOG.md @@ -1,5 +1,12 @@ # @hyperlane-xyz/helloworld +## 3.6.2 + +### Patch Changes + +- @hyperlane-xyz/core@3.6.2 +- @hyperlane-xyz/sdk@3.6.2 + ## 3.6.1 ### Patch Changes diff --git a/typescript/helloworld/package.json b/typescript/helloworld/package.json index 773687366c..ba7ca9c7fc 100644 --- a/typescript/helloworld/package.json +++ b/typescript/helloworld/package.json @@ -1,10 +1,10 @@ { "name": "@hyperlane-xyz/helloworld", "description": "A basic skeleton of an Hyperlane app", - "version": "3.6.1", + "version": "3.6.2", "dependencies": { - "@hyperlane-xyz/core": "3.6.1", - "@hyperlane-xyz/sdk": "3.6.1", + "@hyperlane-xyz/core": "3.6.2", + "@hyperlane-xyz/sdk": "3.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 7d1fb96962..7e7210dca9 100644 --- a/typescript/infra/CHANGELOG.md +++ b/typescript/infra/CHANGELOG.md @@ -1,5 +1,13 @@ # @hyperlane-xyz/infra +## 3.6.2 + +### Patch Changes + +- @hyperlane-xyz/helloworld@3.6.2 +- @hyperlane-xyz/sdk@3.6.2 +- @hyperlane-xyz/utils@3.6.2 + ## 3.6.1 ### Patch Changes diff --git a/typescript/infra/config/aw-multisig.json b/typescript/infra/config/aw-multisig.json new file mode 100644 index 0000000000..9d450aca12 --- /dev/null +++ b/typescript/infra/config/aw-multisig.json @@ -0,0 +1,86 @@ +{ + "alfajores": { + "validators": [ + "0x2233a5ce12f814bd64c9cdd73410bb8693124d40", + "0xba279f965489d90f90490e3c49e860e0b43c2ae6", + "0x86485dcec5f7bb8478dd251676372d054dea6653" + ] + }, + "basegoerli": { + "validators": [ + "0xf6eddda696dcd3bf10f7ce8a02db31ef2e775a03", + "0x5a7d05cebf5db4dde9b2fedcefa76fb58fa05071", + "0x9260a6c7d54cbcbed28f8668679cd1fa3a203b25" + ] + }, + "fuji": { + "validators": [ + "0xd8154f73d04cc7f7f0c332793692e6e6f6b2402e", + "0x895ae30bc83ff1493b9cf7781b0b813d23659857", + "0x43e915573d9f1383cbf482049e4a012290759e7f" + ] + }, + "mumbai": { + "validators": [ + "0xebc301013b6cd2548e347c28d2dc43ec20c068f2", + "0x315db9868fc8813b221b1694f8760ece39f45447", + "0x17517c98358c5937c5d9ee47ce1f5b4c2b7fc9f5" + ] + }, + "bsctestnet": { + "validators": [ + "0x242d8a855a8c932dec51f7999ae7d1e48b10c95e", + "0xf620f5e3d25a3ae848fec74bccae5de3edcd8796", + "0x1f030345963c54ff8229720dd3a711c15c554aeb" + ] + }, + "goerli": { + "validators": [ + "0x05a9b5efe9f61f9142453d8e9f61565f333c6768", + "0x43a96c7dfbd8187c95013d6ee8665650cbdb2673", + "0x7940a12c050e24e1839c21ecb12f65afd84e8c5b" + ] + }, + "scrollsepolia": { + "validators": [ + "0xbe18dbd758afb367180260b524e6d4bcd1cb6d05", + "0x9a11ed23ae962974018ab45bc133caabff7b3271", + "0x7867bea3c9761fe64e6d124b171f91fd5dd79644" + ] + }, + "sepolia": { + "validators": [ + "0xb22b65f202558adf86a8bb2847b76ae1036686a5", + "0x469f0940684d147defc44f3647146cb90dd0bc8e", + "0xd3c75dcf15056012a4d74c483a0c6ea11d8c2b83" + ] + }, + "moonbasealpha": { + "validators": [ + "0x521877064bd7ac7500d300f162c8c47c256a2f9c", + "0xbc1c70f58ae0459d4b8a013245420a893837d568", + "0x01e42c2c44af81dda1ac16fec76fea2a7a54a44c" + ] + }, + "optimismgoerli": { + "validators": [ + "0x79e58546e2faca865c6732ad5f6c4951051c4d67", + "0x7bbfe1bb7146aad7df309c637987d856179ebbc1", + "0xf3d2fb4d53c2bb6a88cec040e0d87430fcee4e40" + ] + }, + "arbitrumgoerli": { + "validators": [ + "0x071c8d135845ae5a2cb73f98d681d519014c0a8b", + "0x1bcf03360989f15cbeb174c188288f2c6d2760d7", + "0xc1590eaaeaf380e7859564c5ebcdcc87e8369e0d" + ] + }, + "polygonzkevmtestnet": { + "validators": [ + "0x3f06b725bc9648917eb11c414e9f8d76fd959550", + "0x27bfc57679d9dd4ab2e870f5ed7ec0b339a0b636", + "0xd476548222f43206d0abaa30e46e28670aa7859c" + ] + } +} diff --git a/typescript/infra/config/environments/agents.ts b/typescript/infra/config/environments/agents.ts new file mode 100644 index 0000000000..f9f7055aa7 --- /dev/null +++ b/typescript/infra/config/environments/agents.ts @@ -0,0 +1,9 @@ +import { agents as mainnet3Agents } from './mainnet3/agent'; +import { agents as testAgents } from './test/agent'; +import { agents as testnet4Agents } from './testnet4/agent'; + +export const agents = { + mainnet3: mainnet3Agents, + testnet4: testnet4Agents, + test: testAgents, +}; diff --git a/typescript/infra/config/environments/helloworld.ts b/typescript/infra/config/environments/helloworld.ts new file mode 100644 index 0000000000..104b3c01ee --- /dev/null +++ b/typescript/infra/config/environments/helloworld.ts @@ -0,0 +1,7 @@ +import { helloWorld as mainnet3HelloWorld } from './mainnet3/helloworld'; +import { helloWorld as testnet4HelloWorld } from './testnet4/helloworld'; + +export const helloworld = { + mainnet3: mainnet3HelloWorld, + testnet4: testnet4HelloWorld, +}; diff --git a/typescript/infra/config/environments/mainnet3/agent.ts b/typescript/infra/config/environments/mainnet3/agent.ts index cef2c5fbcd..ea038e2549 100644 --- a/typescript/infra/config/environments/mainnet3/agent.ts +++ b/typescript/infra/config/environments/mainnet3/agent.ts @@ -13,10 +13,11 @@ import { import { ALL_KEY_ROLES, Role } from '../../../src/roles'; import { Contexts } from '../../contexts'; -import { agentChainNames, environment } from './chains'; +import { agentChainNames, environment, ethereumChainNames } from './chains'; import { helloWorld } from './helloworld'; import { validatorChainConfig } from './validators'; import arbitrumTIAAddresses from './warp/arbitrum-TIA-addresses.json'; +import injectiveInevmAddresses from './warp/injective-inevm-addresses.json'; import mantaTIAAddresses from './warp/manta-TIA-addresses.json'; // const releaseCandidateHelloworldMatchingList = routerMatchingList( @@ -49,7 +50,7 @@ const hyperlane: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: '8ccfdb7-20240103-084118', + tag: '54aeb64-20240206-163119', }, gasPaymentEnforcement, metricAppContexts: [ @@ -59,12 +60,16 @@ const hyperlane: RootAgentConfig = { helloWorld[Contexts.Hyperlane].addresses, ), }, + { + name: 'injective_inevm_inj', + matchingList: routerMatchingList(injectiveInevmAddresses), + }, ], }, validators: { docker: { repo, - tag: '8ccfdb7-20240103-084118', + tag: '54aeb64-20240206-163119', }, rpcConsensusType: RpcConsensusType.Quorum, chains: validatorChainConfig(Contexts.Hyperlane), @@ -73,7 +78,7 @@ const hyperlane: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: '8ccfdb7-20240103-084118', + tag: '54aeb64-20240206-163119', }, }, }; @@ -81,12 +86,16 @@ const hyperlane: RootAgentConfig = { const releaseCandidate: RootAgentConfig = { ...contextBase, context: Contexts.ReleaseCandidate, + contextChainNames: { + ...contextBase.contextChainNames, + [Role.Validator]: ethereumChainNames, + }, rolesWithKeys: [Role.Relayer, Role.Kathy, Role.Validator], relayer: { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: '8ccfdb7-20240103-084118', + tag: '54aeb64-20240206-163119', }, // whitelist: releaseCandidateHelloworldMatchingList, gasPaymentEnforcement, @@ -98,7 +107,7 @@ const releaseCandidate: RootAgentConfig = { validators: { docker: { repo, - tag: '8ccfdb7-20240103-084118', + tag: '54aeb64-20240206-163119', }, rpcConsensusType: RpcConsensusType.Quorum, chains: validatorChainConfig(Contexts.ReleaseCandidate), @@ -122,7 +131,7 @@ const neutron: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: '8ccfdb7-20240103-084118', + tag: '54aeb64-20240206-163119', }, gasPaymentEnforcement: [ { diff --git a/typescript/infra/config/environments/mainnet3/chains.ts b/typescript/infra/config/environments/mainnet3/chains.ts index 2cd00593f8..60a30dbefe 100644 --- a/typescript/infra/config/environments/mainnet3/chains.ts +++ b/typescript/infra/config/environments/mainnet3/chains.ts @@ -30,8 +30,8 @@ export const ethereumMainnetConfigs: ChainMap = { confirmations: 3, }, transactionOverrides: { - maxFeePerGas: 1000 * 10 ** 9, // 500 gwei - maxPriorityFeePerGas: 200 * 10 ** 9, // 100 gwei + maxFeePerGas: 250 * 10 ** 9, // 250 gwei + maxPriorityFeePerGas: 50 * 10 ** 9, // 50 gwei // gasPrice: 50 * 10 ** 9, // 50 gwei }, }, @@ -50,13 +50,14 @@ export const ethereumMainnetConfigs: ChainMap = { // Blessed non-Ethereum chains. export const nonEthereumMainnetConfigs: ChainMap = { - solana: chainMetadata.solana, - neutron: chainMetadata.neutron, + // solana: chainMetadata.solana, + // neutron: chainMetadata.neutron, + injective: chainMetadata.injective, }; export const mainnetConfigs: ChainMap = { ...ethereumMainnetConfigs, - // ...nonEthereumMainnetConfigs, + ...nonEthereumMainnetConfigs, }; export type MainnetChains = keyof typeof mainnetConfigs; @@ -70,14 +71,14 @@ export const ethereumChainNames = Object.keys( ) as MainnetChains[]; // Remove mantapacific, as it's not considered a "blessed" -// chain - we don't relay to mantapacific on the Hyperlane or RC contexts. -const hyperlaneContextRelayChains = ethereumChainNames.filter( +// chain and we don't relay to mantapacific on the Hyperlane or RC contexts. +const relayerHyperlaneContextChains = supportedChainNames.filter( (chainName) => chainName !== Chains.mantapacific, ); -// Skip viction, as it returns incorrect block hashes for eth_getLogs RPCs, -// which breaks the scraper. -const scraperChains = ethereumChainNames.filter( +// Ethereum chains only. +const scraperHyperlaneContextChains = ethereumChainNames.filter( + // Has RPC non-compliance that breaks scraping. (chainName) => chainName !== Chains.viction, ); @@ -85,6 +86,6 @@ const scraperChains = ethereumChainNames.filter( export const agentChainNames: AgentChainNames = { // Run validators for all chains. [Role.Validator]: supportedChainNames, - [Role.Relayer]: hyperlaneContextRelayChains, - [Role.Scraper]: scraperChains, + [Role.Relayer]: relayerHyperlaneContextChains, + [Role.Scraper]: scraperHyperlaneContextChains, }; diff --git a/typescript/infra/config/environments/mainnet3/core/verification.json b/typescript/infra/config/environments/mainnet3/core/verification.json index 7b27ed5bc8..c559233f17 100644 --- a/typescript/infra/config/environments/mainnet3/core/verification.json +++ b/typescript/infra/config/environments/mainnet3/core/verification.json @@ -2048,5 +2048,97 @@ "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", "isProxy": false } + ], + "inevm": [ + { + "name": "ProxyAdmin", + "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "Mailbox", + "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", + "constructorArguments": "00000000000000000000000000000000000000000000000000000000000009dd", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "constructorArguments": "0000000000000000000000004ed7d626f1e96cd1c0401607bf70d95243e3ded10000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true + }, + { + "name": "MerkleTreeHook", + "address": "0x0972954923a1e2b2aAb04Fa0c4a0797e5989Cd65", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", + "isProxy": false + }, + { + "name": "StorageGasOracle", + "address": "0x6119E37Bd66406A1Db74920aC79C15fB8411Ba76", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "InterchainGasPaymaster", + "address": "0x481171eb1aad17eDE6a56005B7F1aB00C581ef13", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0x19dc38aeae620380430C200a6E990D5Af5480117", + "constructorArguments": "000000000000000000000000481171eb1aad17ede6a56005b7f1ab00c581ef130000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true + }, + { + "name": "MerkleTreeHook", + "address": "0x0972954923a1e2b2aAb04Fa0c4a0797e5989Cd65", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", + "isProxy": false + }, + { + "name": "StorageGasOracle", + "address": "0x6119E37Bd66406A1Db74920aC79C15fB8411Ba76", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "InterchainGasPaymaster", + "address": "0x481171eb1aad17eDE6a56005B7F1aB00C581ef13", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0x19dc38aeae620380430C200a6E990D5Af5480117", + "constructorArguments": "000000000000000000000000481171eb1aad17ede6a56005b7f1ab00c581ef130000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true + }, + { + "name": "ProtocolFee", + "address": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", + "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "isProxy": false + }, + { + "name": "ValidatorAnnounce", + "address": "0x15ab173bDB6832f9b64276bA128659b0eD77730B", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", + "isProxy": false + }, + { + "name": "PausableHook", + "address": "0xBDa330Ea8F3005C421C8088e638fBB64fA71b9e0", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "PausableHook", + "address": "0xBDa330Ea8F3005C421C8088e638fBB64fA71b9e0", + "constructorArguments": "", + "isProxy": false + } ] } diff --git a/typescript/infra/config/environments/mainnet3/funding.ts b/typescript/infra/config/environments/mainnet3/funding.ts index ef558f7dad..9207a10d27 100644 --- a/typescript/infra/config/environments/mainnet3/funding.ts +++ b/typescript/infra/config/environments/mainnet3/funding.ts @@ -9,7 +9,7 @@ import { environment } from './chains'; export const keyFunderConfig: KeyFunderConfig = { docker: { repo: 'gcr.io/abacus-labs-dev/hyperlane-monorepo', - tag: 'f6da03e-20231216-141949', + tag: '0e3f73f-20240206-160718', }, // We're currently using the same deployer key as mainnet. // To minimize nonce clobbering we offset the key funder cron @@ -21,7 +21,7 @@ export const keyFunderConfig: KeyFunderConfig = { contextFundingFrom: Contexts.Hyperlane, contextsAndRolesToFund: { [Contexts.Hyperlane]: [Role.Relayer, Role.Kathy], - // [Contexts.ReleaseCandidate]: [Role.Relayer, Role.Kathy], + [Contexts.ReleaseCandidate]: [Role.Relayer, Role.Kathy], }, connectionType: RpcConsensusType.Single, }; diff --git a/typescript/infra/config/environments/mainnet3/gas-oracle.ts b/typescript/infra/config/environments/mainnet3/gas-oracle.ts index fe5c40ebb6..0775a7abc1 100644 --- a/typescript/infra/config/environments/mainnet3/gas-oracle.ts +++ b/typescript/infra/config/environments/mainnet3/gas-oracle.ts @@ -50,6 +50,8 @@ const gasPrices: ChainMap = { polygonzkevm: ethers.utils.parseUnits('2', 'gwei'), neutron: ethers.utils.parseUnits('1', 'gwei'), mantapacific: ethers.utils.parseUnits('1', 'gwei'), + inevm: ethers.utils.parseUnits('1', 'gwei'), + injective: ethers.utils.parseUnits('1', 'gwei'), viction: ethers.utils.parseUnits('0.25', 'gwei'), }; @@ -93,6 +95,9 @@ const tokenUsdPrices: ChainMap = { '1619.00', TOKEN_EXCHANGE_RATE_DECIMALS, ), + // https://www.coingecko.com/en/coins/injective + injective: ethers.utils.parseUnits('32.78', TOKEN_EXCHANGE_RATE_DECIMALS), + inevm: ethers.utils.parseUnits('32.78', TOKEN_EXCHANGE_RATE_DECIMALS), // 1:1 injective // https://www.coingecko.com/en/coins/viction viction: ethers.utils.parseUnits('0.881', TOKEN_EXCHANGE_RATE_DECIMALS), }; diff --git a/typescript/infra/config/environments/mainnet3/helloworld.ts b/typescript/infra/config/environments/mainnet3/helloworld.ts index 011aa767e6..82cf5692ac 100644 --- a/typescript/infra/config/environments/mainnet3/helloworld.ts +++ b/typescript/infra/config/environments/mainnet3/helloworld.ts @@ -34,7 +34,7 @@ export const releaseCandidate: HelloWorldConfig = { kathy: { docker: { repo: 'gcr.io/abacus-labs-dev/hyperlane-monorepo', - tag: '86b7f98-20231207-153806', + tag: '0e3f73f-20240206-160718', }, chainsToSkip: [], runEnv: environment, @@ -44,7 +44,7 @@ export const releaseCandidate: HelloWorldConfig = { }, messageSendTimeout: 1000 * 60 * 8, // 8 min messageReceiptTimeout: 1000 * 60 * 20, // 20 min - connectionType: RpcConsensusType.Single, + connectionType: RpcConsensusType.Fallback, }, }; diff --git a/typescript/infra/config/environments/mainnet3/helloworld/rc/addresses.json b/typescript/infra/config/environments/mainnet3/helloworld/rc/addresses.json index 0967ef424b..d87d332c9a 100644 --- a/typescript/infra/config/environments/mainnet3/helloworld/rc/addresses.json +++ b/typescript/infra/config/environments/mainnet3/helloworld/rc/addresses.json @@ -1 +1,47 @@ -{} +{ + "polygon": { + "router": "0xd5D06f8Ee9cfab362e5758A0A925db7470E7D22f" + }, + "bsc": { + "router": "0xC728F24AA2442d6230c9785635b81fF73C1a16Db" + }, + "arbitrum": { + "router": "0x4D172025D810DDD770e7464A41673ca8e75539b0" + }, + "optimism": { + "router": "0x8Fc3AdBF87c74dF6142f6D65aE0f8BFe042BBDd0" + }, + "moonbeam": { + "router": "0x1dA36d5c79Ae8Bc43eC080FeD9B4Dbb91b509834" + }, + "gnosis": { + "router": "0x9B0C41777A0fC5dd040BC8B043991Eb168f3bD9C" + }, + "base": { + "router": "0x6756189BDE3a29bb56466DECb50BBA76543D8752" + }, + "scroll": { + "router": "0x5366362c41e34869BDa231061603E4356D66079D" + }, + "polygonzkevm": { + "router": "0x03cF708E42C89623bd83B281A56935cB562b9258" + }, + "celo": { + "router": "0xcfacC141f090E5441D8F274659D43ec20F748b19" + }, + "ethereum": { + "router": "0xC6B1e375550343cDA762d2efD4EbdB3B8609a7a4" + }, + "avalanche": { + "router": "0x24fb6e8E7F8298696BaeE10B15bB57295a1f1e35" + }, + "mantapacific": { + "router": "0xDa7cECb05C4aeB02c1aFDE277d4306a2da7Bd762" + }, + "viction": { + "router": "0x83c2DB237e93Ce52565AB110124f78fdf159E3f4" + }, + "inevm": { + "router": "0x0BD07E3934D1C4cc8Db0eA2a5cDAc8C8d8eb9824" + } +} diff --git a/typescript/infra/config/environments/mainnet3/helloworld/rc/verification.json b/typescript/infra/config/environments/mainnet3/helloworld/rc/verification.json index bd533c7149..a6f9a2fe77 100644 --- a/typescript/infra/config/environments/mainnet3/helloworld/rc/verification.json +++ b/typescript/infra/config/environments/mainnet3/helloworld/rc/verification.json @@ -1,127 +1,121 @@ { - "bsc": [ - { - "name": "router", - "address": "0x3f4663873A9aC7Ec683a5Bddc0acbC00091c10D0", - "constructorArguments": "00000000000000000000000035231d4c2d8b8adcb5617a638a0c4548684c7c7000000000000000000000000056f52c0a1ddcd557285f7cbc782d3d83096ce1cc", - "isProxy": false - }, + "polygon": [ { - "name": "router", - "address": "0xe5554478F167936dB253f79f57c41770bfa00Bae", - "constructorArguments": "00000000000000000000000035231d4c2d8b8adcb5617a638a0c4548684c7c7000000000000000000000000056f52c0a1ddcd557285f7cbc782d3d83096ce1cc", + "name": "Router", + "address": "0xd5D06f8Ee9cfab362e5758A0A925db7470E7D22f", + "constructorArguments": "0000000000000000000000005d934f4e2f797775e53561bb72aca21ba36b96bb0000000000000000000000000000000000000000000000000000000000000000", "isProxy": false } ], - "avalanche": [ - { - "name": "router", - "address": "0xaC5a4925d8aab7B9fb33F0a1722e3b94b6f87dB4", - "constructorArguments": "00000000000000000000000035231d4c2d8b8adcb5617a638a0c4548684c7c7000000000000000000000000056f52c0a1ddcd557285f7cbc782d3d83096ce1cc", - "isProxy": false - }, + "bsc": [ { - "name": "router", - "address": "0xe1De9910fe71cC216490AC7FCF019e13a34481D7", - "constructorArguments": "00000000000000000000000035231d4c2d8b8adcb5617a638a0c4548684c7c7000000000000000000000000056f52c0a1ddcd557285f7cbc782d3d83096ce1cc", + "name": "Router", + "address": "0xC728F24AA2442d6230c9785635b81fF73C1a16Db", + "constructorArguments": "0000000000000000000000002971b9aec44be4eb673df1b88cdb57b96eefe8a40000000000000000000000000000000000000000000000000000000000000000", "isProxy": false } ], - "polygon": [ + "arbitrum": [ { - "name": "router", - "address": "0x3Ee06Da5110117c5B9AD41e2F827B774cBb15CC3", - "constructorArguments": "00000000000000000000000035231d4c2d8b8adcb5617a638a0c4548684c7c7000000000000000000000000056f52c0a1ddcd557285f7cbc782d3d83096ce1cc", + "name": "Router", + "address": "0x4D172025D810DDD770e7464A41673ca8e75539b0", + "constructorArguments": "000000000000000000000000979ca5202784112f4738403dbec5d0f3b9daabb90000000000000000000000000000000000000000000000000000000000000000", "isProxy": false - }, + } + ], + "optimism": [ { - "name": "router", - "address": "0xAb65C41a1BC580a52f0b166879122EFdce0cB868", - "constructorArguments": "00000000000000000000000035231d4c2d8b8adcb5617a638a0c4548684c7c7000000000000000000000000056f52c0a1ddcd557285f7cbc782d3d83096ce1cc", + "name": "Router", + "address": "0x8Fc3AdBF87c74dF6142f6D65aE0f8BFe042BBDd0", + "constructorArguments": "000000000000000000000000d4c1905bb1d26bc93dac913e13cacc278cdcc80d0000000000000000000000000000000000000000000000000000000000000000", "isProxy": false } ], - "celo": [ + "moonbeam": [ { - "name": "router", - "address": "0x8E10405F4D23060b1a75005EFB99d99D537e0A7f", - "constructorArguments": "00000000000000000000000035231d4c2d8b8adcb5617a638a0c4548684c7c7000000000000000000000000056f52c0a1ddcd557285f7cbc782d3d83096ce1cc", + "name": "Router", + "address": "0x1dA36d5c79Ae8Bc43eC080FeD9B4Dbb91b509834", + "constructorArguments": "000000000000000000000000094d03e751f49908080eff000dd6fd177fd44cc30000000000000000000000000000000000000000000000000000000000000000", "isProxy": false - }, + } + ], + "gnosis": [ { - "name": "router", - "address": "0xfE29f6a4468536029Fc9c97d3a9669b9fe38E114", - "constructorArguments": "00000000000000000000000035231d4c2d8b8adcb5617a638a0c4548684c7c7000000000000000000000000056f52c0a1ddcd557285f7cbc782d3d83096ce1cc", + "name": "Router", + "address": "0x9B0C41777A0fC5dd040BC8B043991Eb168f3bD9C", + "constructorArguments": "000000000000000000000000ad09d78f4c6b9da2ae82b1d34107802d380bb74f0000000000000000000000000000000000000000000000000000000000000000", "isProxy": false } ], - "arbitrum": [ + "base": [ { - "name": "router", - "address": "0xb44a2B834FFdf762051Ee26Eb41531a4A02fA8d0", - "constructorArguments": "00000000000000000000000035231d4c2d8b8adcb5617a638a0c4548684c7c7000000000000000000000000056f52c0a1ddcd557285f7cbc782d3d83096ce1cc", + "name": "Router", + "address": "0x6756189BDE3a29bb56466DECb50BBA76543D8752", + "constructorArguments": "000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d0000000000000000000000000000000000000000000000000000000000000000", "isProxy": false - }, + } + ], + "scroll": [ { - "name": "router", - "address": "0x414B67F62b143d6db6E9b633168Dd6fd4DA20642", - "constructorArguments": "00000000000000000000000035231d4c2d8b8adcb5617a638a0c4548684c7c7000000000000000000000000056f52c0a1ddcd557285f7cbc782d3d83096ce1cc", + "name": "Router", + "address": "0x5366362c41e34869BDa231061603E4356D66079D", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a70000000000000000000000000000000000000000000000000000000000000000", "isProxy": false } ], - "optimism": [ + "polygonzkevm": [ { - "name": "router", - "address": "0xe5F7E241F9bb6A644e88f2ca38fC373196b5392b", - "constructorArguments": "00000000000000000000000035231d4c2d8b8adcb5617a638a0c4548684c7c7000000000000000000000000056f52c0a1ddcd557285f7cbc782d3d83096ce1cc", + "name": "Router", + "address": "0x03cF708E42C89623bd83B281A56935cB562b9258", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e0000000000000000000000000000000000000000000000000000000000000000", "isProxy": false - }, + } + ], + "celo": [ { - "name": "router", - "address": "0xB4caf2CA864B413DAA502fA18A8D48cD0740fC52", - "constructorArguments": "00000000000000000000000035231d4c2d8b8adcb5617a638a0c4548684c7c7000000000000000000000000056f52c0a1ddcd557285f7cbc782d3d83096ce1cc", + "name": "Router", + "address": "0xcfacC141f090E5441D8F274659D43ec20F748b19", + "constructorArguments": "00000000000000000000000050da3b3907a08a24fe4999f4dcf337e8dc7954bb0000000000000000000000000000000000000000000000000000000000000000", "isProxy": false } ], "ethereum": [ { - "name": "router", - "address": "0x43ae568363c4FA6897EE9dF0c9ca445d3872c906", - "constructorArguments": "00000000000000000000000035231d4c2d8b8adcb5617a638a0c4548684c7c7000000000000000000000000056f52c0a1ddcd557285f7cbc782d3d83096ce1cc", - "isProxy": false - }, - { - "name": "router", - "address": "0xed31c20c5517EaC05decD5F6dCd01Fe6d16fD09D", - "constructorArguments": "00000000000000000000000035231d4c2d8b8adcb5617a638a0c4548684c7c7000000000000000000000000056f52c0a1ddcd557285f7cbc782d3d83096ce1cc", + "name": "Router", + "address": "0xC6B1e375550343cDA762d2efD4EbdB3B8609a7a4", + "constructorArguments": "000000000000000000000000c005dc82818d67af737725bd4bf75435d065d2390000000000000000000000000000000000000000000000000000000000000000", "isProxy": false } ], - "moonbeam": [ + "avalanche": [ { - "name": "router", - "address": "0x1Efd1EdC42ef6cf2612F75e6C599C081d650c513", - "constructorArguments": "00000000000000000000000035231d4c2d8b8adcb5617a638a0c4548684c7c7000000000000000000000000056f52c0a1ddcd557285f7cbc782d3d83096ce1cc", + "name": "Router", + "address": "0x24fb6e8E7F8298696BaeE10B15bB57295a1f1e35", + "constructorArguments": "000000000000000000000000ff06afcaabaddd1fb08371f9cca15d73d51febd60000000000000000000000000000000000000000000000000000000000000000", "isProxy": false - }, + } + ], + "mantapacific": [ { - "name": "router", - "address": "0x3eB9eE2CFC8DCB6F58B5869D33336CFcBf1dC354", - "constructorArguments": "00000000000000000000000035231d4c2d8b8adcb5617a638a0c4548684c7c7000000000000000000000000056f52c0a1ddcd557285f7cbc782d3d83096ce1cc", + "name": "Router", + "address": "0xDa7cECb05C4aeB02c1aFDE277d4306a2da7Bd762", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e0000000000000000000000000000000000000000000000000000000000000000", "isProxy": false } ], - "gnosis": [ + "viction": [ { - "name": "router", - "address": "0x14aBb1f28B06272c57c37723D0e671d1c3326679", - "constructorArguments": "00000000000000000000000035231d4c2d8b8adcb5617a638a0c4548684c7c7000000000000000000000000056f52c0a1ddcd557285f7cbc782d3d83096ce1cc", + "name": "Router", + "address": "0x83c2DB237e93Ce52565AB110124f78fdf159E3f4", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a70000000000000000000000000000000000000000000000000000000000000000", "isProxy": false - }, + } + ], + "inevm": [ { - "name": "router", - "address": "0x99ca8c74cE7Cfa9d72A51fbb05F9821f5f826b3a", - "constructorArguments": "00000000000000000000000035231d4c2d8b8adcb5617a638a0c4548684c7c7000000000000000000000000056f52c0a1ddcd557285f7cbc782d3d83096ce1cc", + "name": "Router", + "address": "0x0BD07E3934D1C4cc8Db0eA2a5cDAc8C8d8eb9824", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a70000000000000000000000000000000000000000000000000000000000000000", "isProxy": false } ] diff --git a/typescript/infra/config/environments/mainnet3/igp.ts b/typescript/infra/config/environments/mainnet3/igp.ts index 19a894d136..d5e999abfa 100644 --- a/typescript/infra/config/environments/mainnet3/igp.ts +++ b/typescript/infra/config/environments/mainnet3/igp.ts @@ -18,6 +18,8 @@ import { owners } from './owners'; const KEY_FUNDER_ADDRESS = '0xa7ECcdb9Be08178f896c26b7BbD8C3D4E844d9Ba'; const DEPLOYER_ADDRESS = '0xa7ECcdb9Be08178f896c26b7BbD8C3D4E844d9Ba'; +const FOREIGN_DEFAULT_OVERHEAD = 600_000; // cosmwasm warp route somewhat arbitrarily chosen + function getGasOracles(local: MainnetChains) { return Object.fromEntries( exclude(local, supportedChainNames).map((name) => [ @@ -27,19 +29,23 @@ function getGasOracles(local: MainnetChains) { ); } -export const igp: ChainMap = objMap(owners, (chain, owner) => ({ +const remoteOverhead = (remote: MainnetChains) => + ethereumChainNames.includes(remote) + ? multisigIsmVerificationCost( + defaultMultisigConfigs[remote].threshold, + defaultMultisigConfigs[remote].validators.length, + ) + : FOREIGN_DEFAULT_OVERHEAD; // non-ethereum overhead + +export const igp: ChainMap = objMap(owners, (local, owner) => ({ ...owner, oracleKey: DEPLOYER_ADDRESS, beneficiary: KEY_FUNDER_ADDRESS, - gasOracleType: getGasOracles(chain), + gasOracleType: getGasOracles(local), overhead: Object.fromEntries( - // Not setting overhead for non-Ethereum destination chains - exclude(chain, ethereumChainNames).map((remote) => [ + exclude(local, supportedChainNames).map((remote) => [ remote, - multisigIsmVerificationCost( - defaultMultisigConfigs[remote].threshold, - defaultMultisigConfigs[remote].validators.length, - ), + remoteOverhead(remote), ]), ), })); diff --git a/typescript/infra/config/environments/mainnet3/index.ts b/typescript/infra/config/environments/mainnet3/index.ts index 3253309025..29282e3839 100644 --- a/typescript/infra/config/environments/mainnet3/index.ts +++ b/typescript/infra/config/environments/mainnet3/index.ts @@ -1,9 +1,10 @@ -import { RpcConsensusType } from '@hyperlane-xyz/sdk'; +import { ChainMetadata, RpcConsensusType } from '@hyperlane-xyz/sdk'; +import { ProtocolType, objFilter } from '@hyperlane-xyz/utils'; import { getKeysForRole, getMultiProviderForRole, -} from '../../../scripts/utils'; +} from '../../../scripts/agent-utils'; import { EnvironmentConfig } from '../../../src/config'; import { Role } from '../../../src/roles'; import { Contexts } from '../../contexts'; @@ -26,15 +27,22 @@ export const environment: EnvironmentConfig = { context: Contexts = Contexts.Hyperlane, role: Role = Role.Deployer, connectionType?: RpcConsensusType, - ) => - getMultiProviderForRole( + ) => { + const config = objFilter( mainnetConfigs, + (_, chainMetadata): chainMetadata is ChainMetadata => + chainMetadata.protocol === ProtocolType.Ethereum, + ); + + return getMultiProviderForRole( + config, environmentName, context, role, undefined, connectionType, - ), + ); + }, getKeys: ( context: Contexts = Contexts.Hyperlane, role: Role = Role.Deployer, diff --git a/typescript/infra/config/environments/mainnet3/ism/verification.json b/typescript/infra/config/environments/mainnet3/ism/verification.json index e3941bf803..1b8f42b360 100644 --- a/typescript/infra/config/environments/mainnet3/ism/verification.json +++ b/typescript/infra/config/environments/mainnet3/ism/verification.json @@ -2244,5 +2244,67 @@ "constructorArguments": "", "isProxy": true } + ], + "inevm": [ + { + "name": "MerkleRootMultisigIsmFactory", + "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0x4725F7b8037513915aAf6D6CBDE2920E28540dDc", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "MessageIdMultisigIsmFactory", + "address": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0xAF03386044373E2fe26C5b1dCedF5a7e854a7a3F", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "AggregationIsmFactory", + "address": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticAggregationIsm", + "address": "0x882CD0C5D50b6dD74b36Da4BDb059507fddEDdf2", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "AggregationHookFactory", + "address": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticAggregationHook", + "address": "0x19930232E9aFC4f4F09d09fe2375680fAc2100D0", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "RoutingIsmFactory", + "address": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "DomaingRoutingIsm", + "address": "0x12Ed1BbA182CbC63692F813651BD493B7445C874", + "constructorArguments": "", + "isProxy": true + } ] } diff --git a/typescript/infra/config/environments/mainnet3/owners.ts b/typescript/infra/config/environments/mainnet3/owners.ts index b01cb0ed7e..9794189456 100644 --- a/typescript/infra/config/environments/mainnet3/owners.ts +++ b/typescript/infra/config/environments/mainnet3/owners.ts @@ -1,5 +1,7 @@ import { ChainMap, OwnableConfig } from '@hyperlane-xyz/sdk'; -import { Address, objMap } from '@hyperlane-xyz/utils'; +import { Address } from '@hyperlane-xyz/utils'; + +import { ethereumChainNames } from './chains'; export const timelocks: ChainMap
= { arbitrum: '0xAC98b0cD1B64EA4fe133C6D2EDaf842cE5cF4b01', @@ -16,19 +18,18 @@ export const safes: ChainMap
= { moonbeam: '0xF0cb1f968Df01fc789762fddBfA704AE0F952197', gnosis: '0x36b0AA0e7d04e7b825D7E409FEa3c9A3d57E4C22', // solana: 'EzppBFV2taxWw8kEjxNYvby6q7W1biJEqwP3iC7YgRe3', - // TODO: create gnosis safes here - base: undefined, - scroll: undefined, - polygonzkevm: undefined, - mantapacific: undefined, - viction: undefined, }; const deployer = '0xa7ECcdb9Be08178f896c26b7BbD8C3D4E844d9Ba'; -export const owners: ChainMap = objMap(safes, (local, __) => ({ - owner: deployer, // TODO: change this to the safe - ownerOverrides: { - proxyAdmin: timelocks[local] ?? safes[local] ?? deployer, - }, -})); +export const owners: ChainMap = Object.fromEntries( + ethereumChainNames.map((local) => [ + local, + { + owner: deployer, // TODO: change this to the safe + ownerOverrides: { + proxyAdmin: timelocks[local] ?? safes[local] ?? deployer, + }, + }, + ]), +); diff --git a/typescript/infra/config/environments/mainnet3/validators.ts b/typescript/infra/config/environments/mainnet3/validators.ts index e7fe44e984..19303db0e8 100644 --- a/typescript/infra/config/environments/mainnet3/validators.ts +++ b/typescript/infra/config/environments/mainnet3/validators.ts @@ -21,7 +21,11 @@ export const validatorChainConfig = ( '0x2f4e808744df049d8acc050628f7bdd8265807f9', '0x7bf30afcb6a7d92146d5a910ea4c154fba38d25e', ], - [Contexts.ReleaseCandidate]: [], + [Contexts.ReleaseCandidate]: [ + '0xb51768c1388e976486a43dbbbbf9ce04cf45e990', + '0x6325de37b33e20089c091950518a471e29c52883', + '0xd796c1d4fcfb3c63acfa6e4113aa6ae1399b337c', + ], [Contexts.Neutron]: [], }, 'celo', @@ -37,7 +41,11 @@ export const validatorChainConfig = ( '0x4346776b10f5e0d9995d884b7a1dbaee4e24c016', '0x749d6e7ad949e522c92181dc77f7bbc1c5d71506', ], - [Contexts.ReleaseCandidate]: [], + [Contexts.ReleaseCandidate]: [ + '0x0580884289890805802012b9872afa5ae41a5fa6', + '0xa5465cb5095a2e6093587e644d6121d6ed55c632', + '0x87cf8a85465118aff9ec728ca157798201b1e368', + ], [Contexts.Neutron]: [], }, 'ethereum', @@ -54,7 +62,9 @@ export const validatorChainConfig = ( '0x6c754f1e9cd8287088b46a7c807303d55d728b49', ], [Contexts.ReleaseCandidate]: [ - '0x706976391e23dea28152e0207936bd942aba01ce', + '0x2c7cf6d1796e37676ba95f056ff21bf536c6c2d3', + '0xcd250d48d16e2ce4b939d44b5215f9e978975152', + '0x26691cd3e9c1b8a82588606b31d9d69b14cb2729', ], [Contexts.Neutron]: [], }, @@ -71,7 +81,11 @@ export const validatorChainConfig = ( '0x8dd8f8d34b5ecaa5f66de24b01acd7b8461c3916', '0xdbf3666de031bea43ec35822e8c33b9a9c610322', ], - [Contexts.ReleaseCandidate]: [], + [Contexts.ReleaseCandidate]: [ + '0xf0a990959f833ccde624c8bcd4c7669286a57a0f', + '0x456b636bdde99d69176261d7a4fba42c16f57f56', + '0xe78d3681d4f59e0768be8b1171f920ed4d52409f', + ], [Contexts.Neutron]: [], }, 'polygon', @@ -87,7 +101,11 @@ export const validatorChainConfig = ( '0x7bf928d5d262365d31d64eaa24755d48c3cae313', '0x03047213365800f065356b4a2fe97c3c3a52296a', ], - [Contexts.ReleaseCandidate]: [], + [Contexts.ReleaseCandidate]: [ + '0x911dfcc19dd5b723e84be452f6af52adef020bc8', + '0xee2d4fd5fe2170e51c6279552297117feaeb19e1', + '0x50ff94984161976a13e9ec3b2a7647da5319448f', + ], [Contexts.Neutron]: [], }, 'bsc', @@ -103,7 +121,11 @@ export const validatorChainConfig = ( '0x6333e110b8a261cab28acb43030bcde59f26978a', '0x3369e12edd52570806f126eb50be269ba5e65843', ], - [Contexts.ReleaseCandidate]: [], + [Contexts.ReleaseCandidate]: [ + '0xb4c18167c163391facb345bb069d12d0430a6a89', + '0x2f6dc057ae079997f76205903b85c8302164a78c', + '0x229d4dc6a740212da746b0e35314419a24bc2a5b', + ], [Contexts.Neutron]: [], }, 'arbitrum', @@ -120,7 +142,9 @@ export const validatorChainConfig = ( '0x779a17e035018396724a6dec8a59bda1b5adf738', ], [Contexts.ReleaseCandidate]: [ - '0x60e938bf280bbc21bacfd8bf435459d9003a8f98', + '0x7e4391786e0b5b0cbaada12d32c931e46e44f104', + '0x138ca73e805afa14e85d80f6e35c46e6f235429e', + '0x2d58cdb2bed9aac57b488b1bad06839ddc280a78', ], [Contexts.Neutron]: [], }, @@ -137,7 +161,11 @@ export const validatorChainConfig = ( '0x4fe067bb455358e295bfcfb92519a6f9de94b98e', '0xcc4a78aa162482bea43313cd836ba7b560b44fc4', ], - [Contexts.ReleaseCandidate]: [], + [Contexts.ReleaseCandidate]: [ + '0x75e3cd4e909089ae6c9f3a42b1468b33eec84161', + '0xc28418d0858a82a46a11e07db75f8bf4eed43881', + '0xcaa9c6e6efa35e4a8b47565f3ce98845fa638bf3', + ], [Contexts.Neutron]: [], }, 'moonbeam', @@ -153,7 +181,11 @@ export const validatorChainConfig = ( '0x06a833508579f8b59d756b3a1e72451fc70840c3', '0xb93a72cee19402553c9dd7fed2461aebd04e2454', ], - [Contexts.ReleaseCandidate]: [], + [Contexts.ReleaseCandidate]: [ + '0xd5122daa0c3dfc94a825ae928f3ea138cdb6a2e1', + '0x2d1f367e942585f8a1c25c742397dc8be9a61dee', + '0x2111141b7f985d305f392c502ad52dd74ef9c569', + ], [Contexts.Neutron]: [], }, 'gnosis', @@ -169,12 +201,52 @@ export const validatorChainConfig = ( '0x4512985a574cb127b2af2d4bb676876ce804e3f8', '0xb144bb2f599a5af095bc30367856f27ea8a8adc7', ], - [Contexts.ReleaseCandidate]: [], + [Contexts.ReleaseCandidate]: [ + '0xa8363570749080c7faa1de714e0782ff444af4cc', + '0x3b55d9febe02a9038ef8c867fa8bbfdd8d70f9b8', + '0xed7703e06572768bb09e03d88e6b788d8800b9fb', + ], [Contexts.Neutron]: [], }, 'base', ), }, + injective: { + interval: 5, + reorgPeriod: getReorgPeriod(chainMetadata.injective), + validators: validatorsConfig( + { + [Contexts.Hyperlane]: [ + '0xbfb8911b72cfb138c7ce517c57d9c691535dc517', + '0x6faa139c33a7e6f53cb101f6b2ae392298283ed2', + '0x0115e3a66820fb99da30d30e2ce52a453ba99d92', + ], + [Contexts.ReleaseCandidate]: [], + [Contexts.Neutron]: [], + }, + 'injective', + ), + }, + inevm: { + interval: 5, + reorgPeriod: getReorgPeriod(chainMetadata.inevm), + validators: validatorsConfig( + { + [Contexts.Hyperlane]: [ + '0xf9e35ee88e4448a3673b4676a4e153e3584a08eb', + '0xae3e6bb6b3ece1c425aa6f47adc8cb0453c1f9a2', + '0xd98c9522cd9d3e3e00bee05ff76c34b91b266ec3', + ], + [Contexts.ReleaseCandidate]: [ + '0x52a0376903294c796c091c785a66c62943d99aa8', + '0xc2ea1799664f753bedb9872d617e3ebc60b2e0ab', + '0xe83d36fd00d9ef86243d9f7147b29e98d11df0ee', + ], + [Contexts.Neutron]: [], + }, + 'inevm', + ), + }, scroll: { interval: 5, reorgPeriod: getReorgPeriod(chainMetadata.scroll), @@ -185,7 +257,11 @@ export const validatorChainConfig = ( '0xb37fe43a9f47b7024c2d5ae22526cc66b5261533', '0x7210fa0a6be39a75cb14d682ebfb37e2b53ecbe5', ], - [Contexts.ReleaseCandidate]: [], + [Contexts.ReleaseCandidate]: [ + '0x11387d89856219cf685f22781bf4e85e00468d54', + '0x64b98b96ccae6e660ecf373b5dd61bcc34fd19ee', + '0x07c2f32a402543badc3141f6b98969d75ef2ac28', + ], [Contexts.Neutron]: [], }, 'scroll', @@ -201,7 +277,11 @@ export const validatorChainConfig = ( '0xc84076030bdabaabb9e61161d833dd84b700afda', '0x6a1da2e0b7ae26aaece1377c0a4dbe25b85fa3ca', ], - [Contexts.ReleaseCandidate]: [], + [Contexts.ReleaseCandidate]: [ + '0x75cffb90391d7ecf58a84e9e70c67e7b306211c0', + '0x82c10acb56f3d7ed6738b61668111a6b5250283e', + '0x1cd73544c000fd519784f56e59bc380a5fef53d6', + ], [Contexts.Neutron]: [], }, 'polygonzkevm', @@ -217,7 +297,11 @@ export const validatorChainConfig = ( '0x60e890b34cb44ce3fa52f38684f613f31b47a1a6', '0x7885fae56dbcf5176657f54adbbd881dc6714132', ], - [Contexts.ReleaseCandidate]: [], + [Contexts.ReleaseCandidate]: [ + '0x307a8fe091b8273c7ce3d277b161b4a2167279b1', + '0xb825c1bd020cb068f477b320f591b32e26814b5b', + '0x0a5b31090d4c3c207b9ea6708f938e328f895fce', + ], [Contexts.Neutron]: [], }, 'neutron', @@ -233,7 +317,11 @@ export const validatorChainConfig = ( '0x80afdde2a81f3fb056fd088a97f0af3722dbc4f3', '0x5dda0c4cf18de3b3ab637f8df82b24921082b54c', ], - [Contexts.ReleaseCandidate]: [], + [Contexts.ReleaseCandidate]: [ + '0x84fcb05e6e5961df2dfd9f36e8f2b3e87ede7d76', + '0x45f3e2655a08feda821ee7b495cf2595401e1569', + '0x4cfccfd66dbb702b643b56f6986a928ed1b50c7e', + ], [Contexts.Neutron]: [], }, 'mantapacific', @@ -249,7 +337,11 @@ export const validatorChainConfig = ( '0x4a2ebbe07cd546cfd2b213d41f2d7814f9386157', '0x00271cf10759e4c6d2f8ca46183ab10d360474b4', ], - [Contexts.ReleaseCandidate]: [], + [Contexts.ReleaseCandidate]: [ + '0xe858971cd865b11d3e8fb6b6af72db0d85881baf', + '0xad94659e2383214e4a1c4e8d3c17caffb75bc31b', + '0x0f9e5775ac4d3b73dd28e5a3f8394443186cb70c', + ], [Contexts.Neutron]: [], }, 'viction', diff --git a/typescript/infra/config/environments/mainnet3/warp/addresses.json b/typescript/infra/config/environments/mainnet3/warp/addresses.json index 79b01185bb..a84f3a7094 100644 --- a/typescript/infra/config/environments/mainnet3/warp/addresses.json +++ b/typescript/infra/config/environments/mainnet3/warp/addresses.json @@ -1,5 +1,5 @@ { - "arbitrum": { - "router": "0x93ca0d85837FF83158Cd14D65B169CdB223b1921" + "inevm": { + "router": "0x26f32245fCF5Ad53159E875d5Cae62aEcf19c2d4" } } diff --git a/typescript/infra/config/environments/mainnet3/warp/injective-inevm-addresses.json b/typescript/infra/config/environments/mainnet3/warp/injective-inevm-addresses.json new file mode 100644 index 0000000000..d92b53abf8 --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/warp/injective-inevm-addresses.json @@ -0,0 +1,8 @@ +{ + "injective": { + "router": "inj1mv9tjvkaw7x8w8y9vds8pkfq46g2vcfkjehc6k" + }, + "inevm": { + "router": "0x26f32245fCF5Ad53159E875d5Cae62aEcf19c2d4" + } +} diff --git a/typescript/infra/config/environments/mainnet3/warp/injective-inevm-deployments.yaml b/typescript/infra/config/environments/mainnet3/warp/injective-inevm-deployments.yaml new file mode 100644 index 0000000000..efabaf59da --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/warp/injective-inevm-deployments.yaml @@ -0,0 +1,22 @@ +# Configs and artifacts for the deployment of Hyperlane Warp Routes +# Between injective and inevm +description: Hyperlane Warp Route artifacts +timestamp: '2024-01-31T16:00:00.000Z' +deployer: Abacus Works (Hyperlane) +data: + config: + injective: + protocolType: cosmos + type: native + hypAddress: inj1mv9tjvkaw7x8w8y9vds8pkfq46g2vcfkjehc6k + name: Injective Coin + symbol: INJ + decimals: 18 + ibcDenom: inj + inevm: + protocolType: ethereum + type: native + hypAddress: '0x26f32245fCF5Ad53159E875d5Cae62aEcf19c2d4' + name: Injective coin + symbol: INJ + decimals: 18 diff --git a/typescript/infra/config/environments/mainnet3/warp/nautilus-solana-bsc-deployments.yaml b/typescript/infra/config/environments/mainnet3/warp/nautilus-solana-bsc-deployments.yaml new file mode 100644 index 0000000000..e3e8770078 --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/warp/nautilus-solana-bsc-deployments.yaml @@ -0,0 +1,31 @@ +# Configs and artifacts for the deployment of Hyperlane Warp Routes +# Between nautilus and bsc, solana +description: Hyperlane Warp Route artifacts +timestamp: '2023-09-23T16:00:00.000Z' +deployer: Abacus Works (Hyperlane) +data: + config: + bsc: + protocolType: ethereum + type: collateral + hypAddress: '0xC27980812E2E66491FD457D488509b7E04144b98' + tokenAddress: '0x37a56cdcD83Dce2868f721De58cB3830C44C6303' + name: Zebec + symbol: ZBC + decimals: 9 + nautilus: + protocolType: ethereum + type: native + hypAddress: '0x4501bBE6e731A4bC5c60C03A77435b2f6d5e9Fe7' + name: Zebec + symbol: ZBC + decimals: 18 + solana: + protocolType: sealevel + type: collateral + tokenAddress: 'wzbcJyhGhQDLTV1S99apZiiBdE4jmYfbw99saMMdP59' + hypAddress: 'EJqwFjvVJSAxH8Ur2PYuMfdvoJeutjmH6GkoEFQ4MdSa' + name: Zebec + symbol: ZBC + decimals: 9 + isSpl2022: true diff --git a/typescript/infra/config/environments/mainnet3/warp/neutron-mantapacific-deployments.yaml b/typescript/infra/config/environments/mainnet3/warp/neutron-mantapacific-deployments.yaml new file mode 100644 index 0000000000..ae13acf3d7 --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/warp/neutron-mantapacific-deployments.yaml @@ -0,0 +1,22 @@ +# Configs and artifacts for the deployment of Hyperlane Warp Routes +# Between neutron and mantapacific +description: Hyperlane Warp Route artifacts +timestamp: '2023-09-23T16:00:00.000Z' +deployer: Abacus Works (Hyperlane) +data: + config: + neutron: + protocolType: cosmos + type: collateral + hypAddress: neutron1ch7x3xgpnj62weyes8vfada35zff6z59kt2psqhnx9gjnt2ttqdqtva3pa + tokenAddress: ibc/773B4D0A3CD667B2275D5A4A7A2F0909C0BA0F4059C0B9181E680DDF4965DCC7 + name: Celestia + symbol: TIA + decimals: 6 + mantapacific: + protocolType: ethereum + type: synthetic + hypAddress: '0x6Fae4D9935E2fcb11fC79a64e917fb2BF14DaFaa' + name: Celestia + symbol: TIA + decimals: 6 diff --git a/typescript/infra/config/environments/testnet4/funding.ts b/typescript/infra/config/environments/testnet4/funding.ts index 75ec4ddf00..78b39dcc49 100644 --- a/typescript/infra/config/environments/testnet4/funding.ts +++ b/typescript/infra/config/environments/testnet4/funding.ts @@ -9,7 +9,7 @@ import { environment } from './chains'; export const keyFunderConfig: KeyFunderConfig = { docker: { repo: 'gcr.io/abacus-labs-dev/hyperlane-monorepo', - tag: 'f6da03e-20231216-141949', + tag: '3a165e4-20240131-141310', }, // We're currently using the same deployer key as testnet2. // To minimize nonce clobbering we offset the key funder cron diff --git a/typescript/infra/config/environments/testnet4/helloworld.ts b/typescript/infra/config/environments/testnet4/helloworld.ts index 475a80a709..a93feb0d69 100644 --- a/typescript/infra/config/environments/testnet4/helloworld.ts +++ b/typescript/infra/config/environments/testnet4/helloworld.ts @@ -33,7 +33,7 @@ export const releaseCandidateHelloworld: HelloWorldConfig = { kathy: { docker: { repo: 'gcr.io/abacus-labs-dev/hyperlane-monorepo', - tag: '86b7f98-20231207-153806', + tag: '3a165e4-20240131-141310', }, chainsToSkip: [], runEnv: environment, @@ -43,7 +43,7 @@ export const releaseCandidateHelloworld: HelloWorldConfig = { }, messageSendTimeout: 1000 * 60 * 8, // 8 min messageReceiptTimeout: 1000 * 60 * 20, // 20 min - connectionType: RpcConsensusType.Single, + connectionType: RpcConsensusType.Fallback, }, }; diff --git a/typescript/infra/config/environments/testnet4/helloworld/rc/addresses.json b/typescript/infra/config/environments/testnet4/helloworld/rc/addresses.json index 5a90998c34..bc716a22c2 100644 --- a/typescript/infra/config/environments/testnet4/helloworld/rc/addresses.json +++ b/typescript/infra/config/environments/testnet4/helloworld/rc/addresses.json @@ -1,32 +1,38 @@ { + "basegoerli": { + "router": "0xC83e12EF2627ACE445C298e6eC418684918a6002" + }, + "arbitrumgoerli": { + "router": "0xEAb4d0A8ba9F1d3a3A665b3F1DE05890A72135D3" + }, + "optimismgoerli": { + "router": "0x7A4A358bF134a55920B4E2cdEbB62961C7a48D19" + }, + "scrollsepolia": { + "router": "0xA9425D5cBcD2c83EB2a5BF453EAA18968db3ef77" + }, "alfajores": { - "router": "0x40Adcb03F3C58170b4751c4140636FC6085Ff475" + "router": "0x4D1d8394cBb445A75aE63fDd24421A353B73FF25" }, - "fuji": { - "router": "0xAc003FcDD0EE223664F2A000B5A59D082745700b" + "polygonzkevmtestnet": { + "router": "0xb3D796584fDeBE2321894eeF31e0C3ec52169C61" }, - "mumbai": { - "router": "0xaB0892029C3E7dD4c0235590dc296E618A7b4d03" + "sepolia": { + "router": "0xEB25e6e42B743a815E5C0409007993a828a0565f" + }, + "fuji": { + "router": "0x29d70a6753D3F3E756502dE6dCd393fE85a97b73" }, "bsctestnet": { - "router": "0xd259b0e793535325786675542aB296c451535c27" + "router": "0x643C7A37FB191A8a63BAB40264B251714F527AED" }, "goerli": { - "router": "0x03e9531ae74e8F0f96DE26788a22d35bdaD24185" + "router": "0x916e550aF85E0Ee7A28FAf54b3E1d87f8f4c0Cdd" }, "moonbasealpha": { - "router": "0xE9D6317a10860340f035f3d09052D9d376855bE8" + "router": "0xabB6e0A30acEB8327EcC6D25bABf409081fDF2DA" }, - "optimismgoerli": { - "router": "0x057d38d184d74192B96840D8FbB37e584dDb569A" - }, - "arbitrumgoerli": { - "router": "0xaAF1BF6f2BfaE290ea8615066fd167e396a2f578" - }, - "sepolia": { - "router": "0x6AD4DEBA8A147d000C09de6465267a9047d1c217" - }, - "solanadevnet": { - "router": "DdTMkk9nuqH5LnD56HLkPiKMV3yB3BNEYSQfgmJHa5i7" + "mumbai": { + "router": "0x4d8323Bb5cD72148e826fCAb9B4A9dd09f77C905" } } diff --git a/typescript/infra/config/environments/testnet4/helloworld/rc/verification.json b/typescript/infra/config/environments/testnet4/helloworld/rc/verification.json index cd0fd9f388..640527d559 100644 --- a/typescript/infra/config/environments/testnet4/helloworld/rc/verification.json +++ b/typescript/infra/config/environments/testnet4/helloworld/rc/verification.json @@ -1,73 +1,97 @@ { - "alfajores": [ + "basegoerli": [ { - "name": "router", - "address": "0x40Adcb03F3C58170b4751c4140636FC6085Ff475", - "constructorArguments": "000000000000000000000000cc737a94fecaec165abcf12ded095bb13f037685000000000000000000000000f90cb82a76492614d07b82a7658917f3ac811ac1", + "name": "Router", + "address": "0xC83e12EF2627ACE445C298e6eC418684918a6002", + "constructorArguments": "00000000000000000000000058483b754abb1e8947be63d6b95df75b8249543a0000000000000000000000000000000000000000000000000000000000000000", "isProxy": false } ], - "fuji": [ + "arbitrumgoerli": [ { - "name": "router", - "address": "0xAc003FcDD0EE223664F2A000B5A59D082745700b", - "constructorArguments": "000000000000000000000000cc737a94fecaec165abcf12ded095bb13f037685000000000000000000000000f90cb82a76492614d07b82a7658917f3ac811ac1", + "name": "Router", + "address": "0xEAb4d0A8ba9F1d3a3A665b3F1DE05890A72135D3", + "constructorArguments": "00000000000000000000000013dabc0351407d5aaa0a50003a166a73b4febfdc0000000000000000000000000000000000000000000000000000000000000000", "isProxy": false } ], - "mumbai": [ + "optimismgoerli": [ { - "name": "router", - "address": "0xaB0892029C3E7dD4c0235590dc296E618A7b4d03", - "constructorArguments": "000000000000000000000000cc737a94fecaec165abcf12ded095bb13f037685000000000000000000000000f90cb82a76492614d07b82a7658917f3ac811ac1", + "name": "Router", + "address": "0x7A4A358bF134a55920B4E2cdEbB62961C7a48D19", + "constructorArguments": "000000000000000000000000b5f021728ea6223e3948db2da61d612307945ea20000000000000000000000000000000000000000000000000000000000000000", "isProxy": false } ], - "bsctestnet": [ + "scrollsepolia": [ { - "name": "router", - "address": "0xd259b0e793535325786675542aB296c451535c27", - "constructorArguments": "000000000000000000000000cc737a94fecaec165abcf12ded095bb13f037685000000000000000000000000f90cb82a76492614d07b82a7658917f3ac811ac1", + "name": "Router", + "address": "0xA9425D5cBcD2c83EB2a5BF453EAA18968db3ef77", + "constructorArguments": "0000000000000000000000003c5154a193d6e2955650f9305c8d80c18c814a680000000000000000000000000000000000000000000000000000000000000000", "isProxy": false } ], - "goerli": [ + "alfajores": [ { - "name": "router", - "address": "0x03e9531ae74e8F0f96DE26788a22d35bdaD24185", - "constructorArguments": "000000000000000000000000cc737a94fecaec165abcf12ded095bb13f037685000000000000000000000000f90cb82a76492614d07b82a7658917f3ac811ac1", + "name": "Router", + "address": "0x4D1d8394cBb445A75aE63fDd24421A353B73FF25", + "constructorArguments": "000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e590000000000000000000000000000000000000000000000000000000000000000", "isProxy": false } ], - "moonbasealpha": [ + "polygonzkevmtestnet": [ { - "name": "router", - "address": "0xE9D6317a10860340f035f3d09052D9d376855bE8", - "constructorArguments": "000000000000000000000000cc737a94fecaec165abcf12ded095bb13f037685000000000000000000000000f90cb82a76492614d07b82a7658917f3ac811ac1", + "name": "Router", + "address": "0xb3D796584fDeBE2321894eeF31e0C3ec52169C61", + "constructorArguments": "000000000000000000000000598face78a4302f11e3de0bee1894da0b2cb71f80000000000000000000000000000000000000000000000000000000000000000", "isProxy": false } ], - "optimismgoerli": [ + "sepolia": [ { - "name": "router", - "address": "0x057d38d184d74192B96840D8FbB37e584dDb569A", - "constructorArguments": "000000000000000000000000cc737a94fecaec165abcf12ded095bb13f037685000000000000000000000000f90cb82a76492614d07b82a7658917f3ac811ac1", + "name": "Router", + "address": "0xEB25e6e42B743a815E5C0409007993a828a0565f", + "constructorArguments": "000000000000000000000000ffaef09b3cd11d9b20d1a19becca54eec28847660000000000000000000000000000000000000000000000000000000000000000", "isProxy": false } ], - "arbitrumgoerli": [ + "fuji": [ { - "name": "router", - "address": "0xaAF1BF6f2BfaE290ea8615066fd167e396a2f578", - "constructorArguments": "000000000000000000000000cc737a94fecaec165abcf12ded095bb13f037685000000000000000000000000f90cb82a76492614d07b82a7658917f3ac811ac1", + "name": "Router", + "address": "0x29d70a6753D3F3E756502dE6dCd393fE85a97b73", + "constructorArguments": "0000000000000000000000005b6cff85442b851a8e6eabd2a4e4507b5135b3b00000000000000000000000000000000000000000000000000000000000000000", "isProxy": false } ], - "sepolia": [ + "bsctestnet": [ + { + "name": "Router", + "address": "0x643C7A37FB191A8a63BAB40264B251714F527AED", + "constructorArguments": "000000000000000000000000f9f6f5646f478d5ab4e20b0f910c92f1ccc9cc6d0000000000000000000000000000000000000000000000000000000000000000", + "isProxy": false + } + ], + "goerli": [ + { + "name": "Router", + "address": "0x916e550aF85E0Ee7A28FAf54b3E1d87f8f4c0Cdd", + "constructorArguments": "00000000000000000000000049cfd6ef774acab14814d699e3f7ee36fdfba9320000000000000000000000000000000000000000000000000000000000000000", + "isProxy": false + } + ], + "moonbasealpha": [ + { + "name": "Router", + "address": "0xabB6e0A30acEB8327EcC6D25bABf409081fDF2DA", + "constructorArguments": "00000000000000000000000076189acfa212298d7022624a4633411ee0d2f26f0000000000000000000000000000000000000000000000000000000000000000", + "isProxy": false + } + ], + "mumbai": [ { "name": "Router", - "address": "0x6AD4DEBA8A147d000C09de6465267a9047d1c217", - "constructorArguments": "000000000000000000000000cc737a94fecaec165abcf12ded095bb13f037685000000000000000000000000f987d7edcb5890cb321437d8145e3d51131298b6", + "address": "0x4d8323Bb5cD72148e826fCAb9B4A9dd09f77C905", + "constructorArguments": "0000000000000000000000002d1889fe5b092cd988972261434f7e5f260411150000000000000000000000000000000000000000000000000000000000000000", "isProxy": false } ] diff --git a/typescript/infra/config/environments/testnet4/index.ts b/typescript/infra/config/environments/testnet4/index.ts index 69ca175aed..8dd7c68ec3 100644 --- a/typescript/infra/config/environments/testnet4/index.ts +++ b/typescript/infra/config/environments/testnet4/index.ts @@ -3,7 +3,7 @@ import { RpcConsensusType } from '@hyperlane-xyz/sdk'; import { getKeysForRole, getMultiProviderForRole, -} from '../../../scripts/utils'; +} from '../../../scripts/agent-utils'; import { EnvironmentConfig } from '../../../src/config'; import { Role } from '../../../src/roles'; import { Contexts } from '../../contexts'; diff --git a/typescript/infra/config/environments/testnet4/validators.ts b/typescript/infra/config/environments/testnet4/validators.ts index 90677f9aa2..3617f04796 100644 --- a/typescript/infra/config/environments/testnet4/validators.ts +++ b/typescript/infra/config/environments/testnet4/validators.ts @@ -289,6 +289,18 @@ export const validatorChainConfig = ( 'polygonzkevmtestnet', ), }, + injective: { + interval: 5, + reorgPeriod: getReorgPeriod(chainMetadata.injective), + validators: validatorsConfig( + { + [Contexts.Hyperlane]: ['0x10686BEe585491A0DA5bfCd5ABfbB95Ab4d6c86d'], + [Contexts.ReleaseCandidate]: [], + [Contexts.Neutron]: [], + }, + 'injective', + ), + }, // proteustestnet: { // interval: 5, // reorgPeriod: getReorgPeriod(chainMetadata.proteustestnet), diff --git a/typescript/infra/config/kathy.json b/typescript/infra/config/kathy.json new file mode 100644 index 0000000000..432476055b --- /dev/null +++ b/typescript/infra/config/kathy.json @@ -0,0 +1,17 @@ +{ + "mainnet3": { + "hyperlane": "0x5fb02f40f56d15f0442a39d11a23f73747095b20", + "neutron": "", + "rc": "" + }, + "testnet4": { + "hyperlane": "0x1e8834ff0669b13cf5d37685c5327b82dbae1144", + "neutron": "", + "rc": "0xa623055727c59697961Ee5e35391D2483b26465e" + }, + "test": { + "hyperlane": "", + "neutron": "", + "rc": "" + } +} diff --git a/typescript/infra/config/relayer.json b/typescript/infra/config/relayer.json new file mode 100644 index 0000000000..8613cb1441 --- /dev/null +++ b/typescript/infra/config/relayer.json @@ -0,0 +1,17 @@ +{ + "mainnet3": { + "hyperlane": "0x74cae0ecc47b02ed9b9d32e000fd70b9417970c5", + "neutron": "", + "rc": "0x09b96417602ed6ac76651f7a8c4860e60e3aa6d0" + }, + "testnet4": { + "hyperlane": "0x16626cd24fd1f228a031e48b77602ae25f8930db", + "neutron": "", + "rc": "0x7fe8c60ead4ab10be736f4de2b3090db5a851f16" + }, + "test": { + "hyperlane": "", + "neutron": "", + "rc": "" + } +} diff --git a/typescript/infra/hardhat.config.ts b/typescript/infra/hardhat.config.ts index 404d6e0a00..e6cb48cefa 100644 --- a/typescript/infra/hardhat.config.ts +++ b/typescript/infra/hardhat.config.ts @@ -12,7 +12,7 @@ import { } from '@hyperlane-xyz/sdk'; import { addressToBytes32 } from '@hyperlane-xyz/utils'; -import { Modules, getAddresses } from './scripts/utils'; +import { Modules, getAddresses } from './scripts/agent-utils'; import { sleep } from './src/utils/utils'; enum MailboxHookType { diff --git a/typescript/infra/helm/warp-routes/templates/_helpers.tpl b/typescript/infra/helm/warp-routes/templates/_helpers.tpl index 338d8ad502..285a5842b8 100644 --- a/typescript/infra/helm/warp-routes/templates/_helpers.tpl +++ b/typescript/infra/helm/warp-routes/templates/_helpers.tpl @@ -61,8 +61,8 @@ The warp-routes container command: - ./node_modules/.bin/ts-node - ./typescript/infra/scripts/warp-routes/monitor-warp-routes-balances.ts - - -l + - -v - "10000" - - -c - - {{ .Values.config }} + - -f + - {{ .Values.configFilePath }} {{- end }} diff --git a/typescript/infra/package.json b/typescript/infra/package.json index fe04c9a5ee..2059a0221a 100644 --- a/typescript/infra/package.json +++ b/typescript/infra/package.json @@ -1,7 +1,7 @@ { "name": "@hyperlane-xyz/infra", "description": "Infrastructure utilities for the Hyperlane Network", - "version": "3.6.1", + "version": "3.6.2", "dependencies": { "@arbitrum/sdk": "^3.0.0", "@aws-sdk/client-iam": "^3.74.0", @@ -12,9 +12,9 @@ "@ethersproject/experimental": "^5.7.0", "@ethersproject/hardware-wallets": "^5.7.0", "@ethersproject/providers": "^5.7.2", - "@hyperlane-xyz/helloworld": "3.6.1", - "@hyperlane-xyz/sdk": "3.6.1", - "@hyperlane-xyz/utils": "3.6.1", + "@hyperlane-xyz/helloworld": "3.6.2", + "@hyperlane-xyz/sdk": "3.6.2", + "@hyperlane-xyz/utils": "3.6.2", "@nomiclabs/hardhat-etherscan": "^3.0.3", "@safe-global/api-kit": "^1.3.0", "@safe-global/protocol-kit": "^1.2.0", diff --git a/typescript/infra/scripts/utils.ts b/typescript/infra/scripts/agent-utils.ts similarity index 65% rename from typescript/infra/scripts/utils.ts rename to typescript/infra/scripts/agent-utils.ts index 645aa2ca05..07f590be97 100644 --- a/typescript/infra/scripts/utils.ts +++ b/typescript/infra/scripts/agent-utils.ts @@ -1,3 +1,4 @@ +import debug from 'debug'; import path from 'path'; import yargs from 'yargs'; @@ -10,12 +11,14 @@ import { CoreConfig, MultiProvider, RpcConsensusType, + chainMetadata, collectValidators, } from '@hyperlane-xyz/sdk'; import { ProtocolType, objMap, promiseObjAll } from '@hyperlane-xyz/utils'; import { Contexts } from '../config/contexts'; -import { environments } from '../config/environments'; +import { agents } from '../config/environments/agents'; +import { validatorBaseConfigsFn } from '../config/environments/utils'; import { getCurrentKubernetesContext } from '../src/agents'; import { getCloudAgentKey } from '../src/agents/key-utils'; import { CloudAgentKey } from '../src/agents/keys'; @@ -29,6 +32,8 @@ import { EnvironmentNames, deployEnvToSdkEnv } from '../src/config/environment'; import { Role } from '../src/roles'; import { assertContext, assertRole, readJSON } from '../src/utils/utils'; +const debugLog = debug('infra:scripts:utils'); + export enum Modules { // TODO: change PROXY_FACTORY = 'ism', @@ -120,6 +125,14 @@ export function withKeyRoleAndChain(args: yargs.Argv) { .alias('i', 'index'); } +// missing chains are chains needed which are not as part of defaultMultisigConfigs in sdk/src/consts/ but are in chainMetadata +export function withMissingChains(args: yargs.Argv) { + return args + .describe('newChains', 'new chains to add') + .string('newChains') + .alias('n', 'newChains'); +} + export function assertEnvironment(env: string): DeployEnvironment { if (EnvironmentNames.includes(env)) { return env as DeployEnvironment; @@ -129,39 +142,98 @@ export function assertEnvironment(env: string): DeployEnvironment { ); } -export function getEnvironmentConfig(environment: DeployEnvironment) { - return environments[environment]; -} - -export async function getConfigsBasedOnArgs(argv?: { +// not requiring to build coreConfig to get agentConfig +export async function getAgentConfigsBasedOnArgs(argv?: { environment: DeployEnvironment; context: Contexts; + newChains: string; }) { - const { environment, context = Contexts.Hyperlane } = argv - ? argv - : await withContext(getArgs()).argv; - const envConfig = getEnvironmentConfig(environment); - const agentConfig = getAgentConfig(context, envConfig); - return { envConfig, agentConfig, context, environment }; + const { + environment, + context = Contexts.Hyperlane, + newChains, + } = argv ? argv : await withMissingChains(withContext(getArgs())).argv; + + const newValidatorCounts: ChainMap = {}; + if (newChains) { + const chains = newChains.split(','); + for (const chain of chains) { + const [chainName, newValidatorCount] = chain.split('='); + newValidatorCounts[chainName] = parseInt(newValidatorCount, 10); + } + } + + const agentConfig = getAgentConfig(context, environment); + // check if new chains are needed + const missingChains = checkIfValidatorsArePersisted(agentConfig); + + // if you include a chain in chainMetadata but not in the aw-multisig.json, you need to specify the new chain in new-chains + for (const chain of missingChains) { + if (!Object.keys(newValidatorCounts).includes(chain)) { + throw new Error(`Missing chain ${chain} not specified in new-chains`); + } + const baseConfig = { + [Contexts.Hyperlane]: [], + [Contexts.ReleaseCandidate]: [], + [Contexts.Neutron]: [], + }; + // supplementing with dummy addresses for validator as part of missingChains + const validatorsConfig = validatorBaseConfigsFn(environment, context); + + const validators = validatorsConfig( + { + ...baseConfig, + [context]: Array(newValidatorCounts[chain]).fill('0x0'), + }, + chain as Chains, + ); + // the hardcoded fields are not strictly necessary to be accurate for create-keys.ts + // ideally would still get them from the chainMetadata + if (!agentConfig.validators) { + throw new Error('AgentConfig does not have validators'); + } + + agentConfig.validators.chains[chain] = { + interval: chainMetadata[chain].blocks?.estimateBlockTime ?? 1, // dummy value + reorgPeriod: chainMetadata[chain].blocks?.reorgPeriod ?? 0, // dummy value + validators, + }; + } + + return { + agentConfig, + context, + environment, + }; } // Gets the agent config of a specific context. +// without fetching environment config export function getAgentConfig( context: Contexts, - environment: EnvironmentConfig | DeployEnvironment, + environment: DeployEnvironment, ): RootAgentConfig { - const coreConfig = - typeof environment == 'string' - ? getEnvironmentConfig(environment) - : environment; - const agentConfig = coreConfig.agents[context]; - if (!agentConfig) - throw Error( - `Invalid context ${context} for environment, must be one of ${Object.keys( - coreConfig.agents, - )}.`, + const agentsForEnvironment = agents[environment] as Record< + Contexts, + RootAgentConfig + >; + if (!Object.keys(agents[environment]).includes(context)) { + throw new Error( + `Context ${context} does not exist in agents for environment ${environment}`, ); - return agentConfig; + } + return agentsForEnvironment[context]; +} + +// check if validators are persisted in agentConfig +export function checkIfValidatorsArePersisted( + agentConfig: RootAgentConfig, +): Set { + const supportedChainNames = agentConfig.contextChainNames.validator; + const persistedChainNames = Object.keys(agentConfig.validators?.chains || {}); + return new Set( + supportedChainNames.filter((x) => !persistedChainNames.includes(x)), + ); } export function getKeyForRole( @@ -171,8 +243,8 @@ export function getKeyForRole( role: Role, index?: number, ): CloudAgentKey { - const environmentConfig = environments[environment]; - const agentConfig = getAgentConfig(context, environmentConfig); + debugLog(`Getting key for ${role} role`); + const agentConfig = getAgentConfig(context, environment); return getCloudAgentKey(agentConfig, role, chain, index); } @@ -185,17 +257,25 @@ export async function getMultiProviderForRole( // TODO: rename to consensusType? connectionType?: RpcConsensusType, ): Promise { + debugLog(`Getting multiprovider for ${role} role`); if (process.env.CI === 'true') { + debugLog('Returning multiprovider with default RPCs in CI'); return new MultiProvider(); // use default RPCs } const multiProvider = new MultiProvider(txConfigs); await promiseObjAll( objMap(txConfigs, async (chain, _) => { - const provider = await fetchProvider(environment, chain, connectionType); - const key = getKeyForRole(environment, context, chain, role, index); - const signer = await key.getSigner(provider); - multiProvider.setProvider(chain, provider); - multiProvider.setSigner(chain, signer); + if (multiProvider.getProtocol(chain) === ProtocolType.Ethereum) { + const provider = await fetchProvider( + environment, + chain, + connectionType, + ); + const key = getKeyForRole(environment, context, chain, role, index); + const signer = await key.getSigner(provider); + multiProvider.setProvider(chain, provider); + multiProvider.setSigner(chain, signer); + } }), ); @@ -212,6 +292,7 @@ export async function getKeysForRole( index?: number, ): Promise> { if (process.env.CI === 'true') { + debugLog('No keys to return in CI'); return {}; } diff --git a/typescript/infra/scripts/agents/deploy-agents.ts b/typescript/infra/scripts/agents/deploy-agents.ts index 3802e240b6..f2080b7afd 100644 --- a/typescript/infra/scripts/agents/deploy-agents.ts +++ b/typescript/infra/scripts/agents/deploy-agents.ts @@ -1,6 +1,6 @@ import { createAgentKeysIfNotExists } from '../../src/agents/key-utils'; import { HelmCommand } from '../../src/utils/helm'; -import { getConfigsBasedOnArgs } from '../utils'; +import { getConfigsBasedOnArgs } from '../core-utils'; import { AgentCli } from './utils'; diff --git a/typescript/infra/scripts/agents/utils.ts b/typescript/infra/scripts/agents/utils.ts index ba01666ed8..c351346807 100644 --- a/typescript/infra/scripts/agents/utils.ts +++ b/typescript/infra/scripts/agents/utils.ts @@ -7,14 +7,13 @@ import { import { EnvironmentConfig, RootAgentConfig } from '../../src/config'; import { Role } from '../../src/roles'; import { HelmCommand } from '../../src/utils/helm'; -import { sleep } from '../../src/utils/utils'; import { assertCorrectKubeContext, getArgs, - getConfigsBasedOnArgs, withAgentRole, withContext, -} from '../utils'; +} from '../agent-utils'; +import { getConfigsBasedOnArgs } from '../core-utils'; export class AgentCli { roles!: Role[]; diff --git a/typescript/infra/scripts/announce-validators.ts b/typescript/infra/scripts/announce-validators.ts index 509c0d5237..05cb607f93 100644 --- a/typescript/infra/scripts/announce-validators.ts +++ b/typescript/infra/scripts/announce-validators.ts @@ -12,10 +12,10 @@ import { isEthereumProtocolChain } from '../src/utils/utils'; import { getAgentConfig, - getEnvironmentConfig, getArgs as getRootArgs, withContext, -} from './utils'; +} from './agent-utils'; +import { getEnvironmentConfig } from './core-utils'; function getArgs() { return withContext(getRootArgs()) @@ -75,7 +75,7 @@ async function main() { throw new Error(`Unknown location type %{location}`); } } else { - const agentConfig = getAgentConfig(context, config); + const agentConfig = getAgentConfig(context, environment); if (agentConfig.validators == undefined) { console.warn('No validators provided for context'); return; @@ -126,7 +126,9 @@ async function main() { const announced = announcedLocations[0].includes(location); if (!announced) { const signature = ethers.utils.joinSignature(announcement.signature); - console.log(`Announcing ${address} checkpoints at ${location}`); + console.log( + `[${chain}] Announcing ${address} checkpoints at ${location}`, + ); await validatorAnnounce.announce( address, location, @@ -134,7 +136,9 @@ async function main() { multiProvider.getTransactionOverrides(chain), ); } else { - console.log(`Already announced ${address} checkpoints at ${location}`); + console.log( + `[${chain}] Already announced ${address} checkpoints at ${location}`, + ); } } } diff --git a/typescript/infra/scripts/check-deploy.ts b/typescript/infra/scripts/check-deploy.ts index 75df19b2d8..52b73c34b3 100644 --- a/typescript/infra/scripts/check-deploy.ts +++ b/typescript/infra/scripts/check-deploy.ts @@ -20,14 +20,14 @@ import { ProxiedRouterGovernor } from '../src/govern/ProxiedRouterGovernor'; import { Role } from '../src/roles'; import { impersonateAccount, useLocalProvider } from '../src/utils/fork'; -import { getHelloWorldApp } from './helloworld/utils'; import { Modules, - getEnvironmentConfig, getArgs as getRootArgs, withContext, withModuleAndFork, -} from './utils'; +} from './agent-utils'; +import { getEnvironmentConfig } from './core-utils'; +import { getHelloWorldApp } from './helloworld/utils'; function getArgs() { return withModuleAndFork(withContext(getRootArgs())) diff --git a/typescript/infra/scripts/check-rpc-urls.ts b/typescript/infra/scripts/check-rpc-urls.ts index 48e874de58..daf7f69657 100644 --- a/typescript/infra/scripts/check-rpc-urls.ts +++ b/typescript/infra/scripts/check-rpc-urls.ts @@ -4,7 +4,8 @@ import { debug, error } from '@hyperlane-xyz/utils'; import { getSecretRpcEndpoint } from '../src/agents'; -import { getArgs, getEnvironmentConfig } from './utils'; +import { getArgs } from './agent-utils'; +import { getEnvironmentConfig } from './core-utils'; // TODO remove this script as part of migration to CLI // It's redundant with metadata-check.ts in the SDK diff --git a/typescript/infra/scripts/core-utils.ts b/typescript/infra/scripts/core-utils.ts new file mode 100644 index 0000000000..4c41ccd83b --- /dev/null +++ b/typescript/infra/scripts/core-utils.ts @@ -0,0 +1,23 @@ +import { Contexts } from '../config/contexts'; +import { environments } from '../config/environments'; +import { DeployEnvironment } from '../src/config'; + +import { getAgentConfig, getArgs, withContext } from './agent-utils'; + +// utils which use both environment configs + +export function getEnvironmentConfig(environment: DeployEnvironment) { + return environments[environment]; +} + +export async function getConfigsBasedOnArgs(argv?: { + environment: DeployEnvironment; + context: Contexts; +}) { + const { environment, context = Contexts.Hyperlane } = argv + ? argv + : await withContext(getArgs()).argv; + const envConfig = getEnvironmentConfig(environment); + const agentConfig = getAgentConfig(context, environment); + return { envConfig, agentConfig, context, environment }; +} diff --git a/typescript/infra/scripts/create-keys.ts b/typescript/infra/scripts/create-keys.ts index 38e1df9dcc..f0778501a8 100644 --- a/typescript/infra/scripts/create-keys.ts +++ b/typescript/infra/scripts/create-keys.ts @@ -1,10 +1,11 @@ import { createAgentKeysIfNotExists } from '../src/agents/key-utils'; -import { getConfigsBasedOnArgs } from './utils'; +import { getAgentConfigsBasedOnArgs } from './agent-utils'; async function main() { - const { agentConfig } = await getConfigsBasedOnArgs(); - return createAgentKeysIfNotExists(agentConfig); + const { agentConfig } = await getAgentConfigsBasedOnArgs(); + await createAgentKeysIfNotExists(agentConfig); + return 'Keys created successfully!'; } main().then(console.log).catch(console.error); diff --git a/typescript/infra/scripts/debug-message.ts b/typescript/infra/scripts/debug-message.ts index d49b13cf60..d39a5f1bb9 100644 --- a/typescript/infra/scripts/debug-message.ts +++ b/typescript/infra/scripts/debug-message.ts @@ -10,7 +10,7 @@ import { bytes32ToAddress, ensure0x, messageId } from '@hyperlane-xyz/utils'; import { deployEnvToSdkEnv } from '../src/config/environment'; import { assertChain } from '../src/utils/utils'; -import { getArgs } from './utils'; +import { getArgs } from './agent-utils'; async function main() { const argv = await getArgs() diff --git a/typescript/infra/scripts/delete-keys.ts b/typescript/infra/scripts/delete-keys.ts index 29dfde2e64..e76aed23dd 100644 --- a/typescript/infra/scripts/delete-keys.ts +++ b/typescript/infra/scripts/delete-keys.ts @@ -1,9 +1,9 @@ import { deleteAgentKeys } from '../src/agents/key-utils'; -import { getConfigsBasedOnArgs } from './utils'; +import { getAgentConfigsBasedOnArgs } from './agent-utils'; async function main() { - const { agentConfig } = await getConfigsBasedOnArgs(); + const { agentConfig } = await getAgentConfigsBasedOnArgs(); return deleteAgentKeys(agentConfig); } diff --git a/typescript/infra/scripts/deploy-infra-external-secrets.ts b/typescript/infra/scripts/deploy-infra-external-secrets.ts index d3ac34dd40..cd20245751 100644 --- a/typescript/infra/scripts/deploy-infra-external-secrets.ts +++ b/typescript/infra/scripts/deploy-infra-external-secrets.ts @@ -1,11 +1,8 @@ import { runExternalSecretsHelmCommand } from '../src/infrastructure/external-secrets/external-secrets'; import { HelmCommand } from '../src/utils/helm'; -import { - assertCorrectKubeContext, - getArgs, - getEnvironmentConfig, -} from './utils'; +import { assertCorrectKubeContext, getArgs } from './agent-utils'; +import { getEnvironmentConfig } from './core-utils'; async function main() { const { environment } = await getArgs().argv; diff --git a/typescript/infra/scripts/deploy-infra-monitoring.ts b/typescript/infra/scripts/deploy-infra-monitoring.ts index 06862883a7..81e638cab0 100644 --- a/typescript/infra/scripts/deploy-infra-monitoring.ts +++ b/typescript/infra/scripts/deploy-infra-monitoring.ts @@ -1,11 +1,8 @@ import { runPrometheusHelmCommand } from '../src/infrastructure/monitoring/prometheus'; import { HelmCommand } from '../src/utils/helm'; -import { - assertCorrectKubeContext, - getArgs, - getEnvironmentConfig, -} from './utils'; +import { assertCorrectKubeContext, getArgs } from './agent-utils'; +import { getEnvironmentConfig } from './core-utils'; async function main() { const { environment } = await getArgs().argv; diff --git a/typescript/infra/scripts/deploy.ts b/typescript/infra/scripts/deploy.ts index 1bceb97407..1fdf1ff9fd 100644 --- a/typescript/infra/scripts/deploy.ts +++ b/typescript/infra/scripts/deploy.ts @@ -28,12 +28,12 @@ import { getAddresses, getArgs, getContractAddressesSdkFilepath, - getEnvironmentConfig, getModuleDirectory, withContext, withModuleAndFork, withNetwork, -} from './utils'; +} from './agent-utils'; +import { getEnvironmentConfig } from './core-utils'; async function main() { const { diff --git a/typescript/infra/scripts/funding/deploy-key-funder.ts b/typescript/infra/scripts/funding/deploy-key-funder.ts index d4cc5d80d6..a73104c924 100644 --- a/typescript/infra/scripts/funding/deploy-key-funder.ts +++ b/typescript/infra/scripts/funding/deploy-key-funder.ts @@ -4,7 +4,8 @@ import { runKeyFunderHelmCommand, } from '../../src/funding/key-funder'; import { HelmCommand } from '../../src/utils/helm'; -import { assertCorrectKubeContext, getConfigsBasedOnArgs } from '../utils'; +import { assertCorrectKubeContext } from '../agent-utils'; +import { getConfigsBasedOnArgs } from '../core-utils'; async function main() { const { agentConfig, envConfig } = await getConfigsBasedOnArgs(); diff --git a/typescript/infra/scripts/funding/fund-deterministic-key-from-deployer.ts b/typescript/infra/scripts/funding/fund-deterministic-key-from-deployer.ts index fbbdff95c3..7aad6f55f3 100644 --- a/typescript/infra/scripts/funding/fund-deterministic-key-from-deployer.ts +++ b/typescript/infra/scripts/funding/fund-deterministic-key-from-deployer.ts @@ -10,7 +10,8 @@ import { } from '../../src/funding/deterministic-keys'; import { Role } from '../../src/roles'; import { assertChain } from '../../src/utils/utils'; -import { getArgs, getEnvironmentConfig } from '../utils'; +import { getArgs } from '../agent-utils'; +import { getEnvironmentConfig } from '../core-utils'; async function main() { const argv = await getArgs() diff --git a/typescript/infra/scripts/funding/fund-keys-from-deployer.ts b/typescript/infra/scripts/funding/fund-keys-from-deployer.ts index 75af8d646e..d80fa088da 100644 --- a/typescript/infra/scripts/funding/fund-keys-from-deployer.ts +++ b/typescript/infra/scripts/funding/fund-keys-from-deployer.ts @@ -22,7 +22,6 @@ import { } from '@hyperlane-xyz/utils'; import { Contexts } from '../../config/contexts'; -import { parseKeyIdentifier } from '../../src/agents/agent'; import { KeyAsAddress, getRoleKeysPerChain } from '../../src/agents/key-utils'; import { BaseCloudAgentKey, @@ -39,7 +38,8 @@ import { isEthereumProtocolChain, readJSONAtPath, } from '../../src/utils/utils'; -import { getAgentConfig, getArgs, getEnvironmentConfig } from '../utils'; +import { getAgentConfig, getArgs } from '../agent-utils'; +import { getEnvironmentConfig } from '../core-utils'; import * as L1ETHGateway from './utils/L1ETHGateway.json'; import * as L1MessageQueue from './utils/L1MessageQueue.json'; @@ -167,6 +167,8 @@ const desiredKathyBalancePerChain: ChainMap = { scroll: '0.05', base: '0.05', polygonzkevm: '0.05', + viction: '0.05', + inevm: '0.05', }; // The balance threshold of the IGP contract that must be met for the key funder @@ -605,7 +607,7 @@ class ContextFunder { role === Role.Kathy && desiredKathyBalancePerChain[chain] ? desiredKathyBalancePerChain[chain] : desiredBalancePerChain[chain]; - let desiredBalance = ethers.utils.parseEther(desiredBalanceEther); + let desiredBalance = ethers.utils.parseEther(desiredBalanceEther ?? '0'); if (this.context === Contexts.ReleaseCandidate) { desiredBalance = desiredBalance .mul(RC_FUNDING_DISCOUNT_NUMERATOR) diff --git a/typescript/infra/scripts/funding/reclaim-from-igp.ts b/typescript/infra/scripts/funding/reclaim-from-igp.ts index f780b579a3..87c836054d 100644 --- a/typescript/infra/scripts/funding/reclaim-from-igp.ts +++ b/typescript/infra/scripts/funding/reclaim-from-igp.ts @@ -4,14 +4,15 @@ import { HyperlaneIgp } from '@hyperlane-xyz/sdk'; import { objMap, promiseObjAll } from '@hyperlane-xyz/utils'; import { deployEnvToSdkEnv } from '../../src/config/environment'; -import { getArgs, getEnvironmentConfig } from '../utils'; +import { getArgs } from '../agent-utils'; +import { getEnvironmentConfig } from '../core-utils'; // Some arbitrary threshold for now const RECLAIM_BALANCE_THRESHOLD = BigNumber.from(10).pow(17); async function main() { const { environment } = await getArgs().argv; - const environmentConfig = await getEnvironmentConfig(environment); + const environmentConfig = getEnvironmentConfig(environment); const multiProvider = await environmentConfig.getMultiProvider(); const igp = HyperlaneIgp.fromEnvironment( deployEnvToSdkEnv[environment], diff --git a/typescript/infra/scripts/gas-oracle/compare-token-exchange-rates.ts b/typescript/infra/scripts/gas-oracle/compare-token-exchange-rates.ts index 23e31ba97e..2865af32c1 100644 --- a/typescript/infra/scripts/gas-oracle/compare-token-exchange-rates.ts +++ b/typescript/infra/scripts/gas-oracle/compare-token-exchange-rates.ts @@ -12,7 +12,8 @@ import { TOKEN_EXCHANGE_RATE_DECIMALS, TOKEN_EXCHANGE_RATE_SCALE, } from '../../src/config/gas-oracle'; -import { getArgs, getEnvironmentConfig } from '../utils'; +import { getArgs } from '../agent-utils'; +import { getEnvironmentConfig } from '../core-utils'; import { prettyTokenExchangeRate } from './utils'; @@ -47,7 +48,7 @@ async function compare( localStorageGasOracleConfig: StorageGasOracleConfig, local: ChainName, ) { - for (const remoteStr in localStorageGasOracleConfig) { + for (const remoteStr of Object.keys(localStorageGasOracleConfig)) { const remote = remoteStr as ChainName; const configGasData = localStorageGasOracleConfig[remote]!; const currentTokenExchangeRateNum = diff --git a/typescript/infra/scripts/gas-oracle/update-storage-gas-oracle.ts b/typescript/infra/scripts/gas-oracle/update-storage-gas-oracle.ts index 67082d5095..669211e27c 100644 --- a/typescript/infra/scripts/gas-oracle/update-storage-gas-oracle.ts +++ b/typescript/infra/scripts/gas-oracle/update-storage-gas-oracle.ts @@ -4,7 +4,8 @@ import { ProtocolType } from '@hyperlane-xyz/utils'; import { RemoteGasData, StorageGasOracleConfig } from '../../src/config'; import { deployEnvToSdkEnv } from '../../src/config/environment'; import { RemoteGasDataConfig } from '../../src/config/gas-oracle'; -import { getArgs, getEnvironmentConfig, withNetwork } from '../utils'; +import { getArgs, withNetwork } from '../agent-utils'; +import { getEnvironmentConfig } from '../core-utils'; import { eqRemoteGasData, @@ -73,7 +74,7 @@ async function setStorageGasOracleValues( const configsToSet: RemoteGasDataConfig[] = []; - for (const remote in localStorageGasOracleConfig) { + for (const remote of Object.keys(localStorageGasOracleConfig)) { const desiredGasData = localStorageGasOracleConfig[remote]!; const remoteId = multiProvider.getDomainId(remote); diff --git a/typescript/infra/scripts/get-key-addresses.ts b/typescript/infra/scripts/get-key-addresses.ts index 6864177d6a..ccbc221060 100644 --- a/typescript/infra/scripts/get-key-addresses.ts +++ b/typescript/infra/scripts/get-key-addresses.ts @@ -1,11 +1,7 @@ import { getAllCloudAgentKeys } from '../src/agents/key-utils'; -import { - getArgs, - getConfigsBasedOnArgs, - withContext, - withProtocol, -} from './utils'; +import { getArgs, withContext, withProtocol } from './agent-utils'; +import { getConfigsBasedOnArgs } from './core-utils'; async function main() { const argv = await withProtocol(withContext(getArgs())).argv; diff --git a/typescript/infra/scripts/helloworld/deploy-kathy.ts b/typescript/infra/scripts/helloworld/deploy-kathy.ts index 45720c1365..2cc7bf4435 100644 --- a/typescript/infra/scripts/helloworld/deploy-kathy.ts +++ b/typescript/infra/scripts/helloworld/deploy-kathy.ts @@ -1,6 +1,7 @@ import { runHelloworldKathyHelmCommand } from '../../src/helloworld/kathy'; import { HelmCommand } from '../../src/utils/helm'; -import { assertCorrectKubeContext, getConfigsBasedOnArgs } from '../utils'; +import { assertCorrectKubeContext } from '../agent-utils'; +import { getConfigsBasedOnArgs } from '../core-utils'; import { getHelloWorldConfig } from './utils'; diff --git a/typescript/infra/scripts/helloworld/kathy.ts b/typescript/infra/scripts/helloworld/kathy.ts index 2715e4a4b3..cae5e858a2 100644 --- a/typescript/infra/scripts/helloworld/kathy.ts +++ b/typescript/infra/scripts/helloworld/kathy.ts @@ -40,7 +40,8 @@ import { DeployEnvironment } from '../../src/config/environment'; import { Role } from '../../src/roles'; import { startMetricsServer } from '../../src/utils/metrics'; import { assertChain, diagonalize, sleep } from '../../src/utils/utils'; -import { getArgs, getEnvironmentConfig, withContext } from '../utils'; +import { getArgs, withContext } from '../agent-utils'; +import { getEnvironmentConfig } from '../core-utils'; import { getHelloWorldMultiProtocolApp } from './utils'; diff --git a/typescript/infra/scripts/helloworld/utils.ts b/typescript/infra/scripts/helloworld/utils.ts index cfddb9ee37..834d259d60 100644 --- a/typescript/infra/scripts/helloworld/utils.ts +++ b/typescript/infra/scripts/helloworld/utils.ts @@ -162,3 +162,18 @@ export function getHelloWorldConfig( } return config; } + +// for create-key, you don't want to fetch the multisig[chain].validators.threshold for yet to be created multisigs +export function getJustHelloWorldConfig( + helloWorldConfigs: Partial> | undefined, + context: Contexts, +): HelloWorldConfig { + if (!helloWorldConfigs) { + throw new Error(`Environment does not have a HelloWorld config`); + } + const config = helloWorldConfigs[context]; + if (!config) { + throw new Error(`Context ${context} does not have a HelloWorld config`); + } + return config; +} diff --git a/typescript/infra/scripts/list-validator-checkpoint-indices.ts b/typescript/infra/scripts/list-validator-checkpoint-indices.ts index e918cb6027..d423861b34 100644 --- a/typescript/infra/scripts/list-validator-checkpoint-indices.ts +++ b/typescript/infra/scripts/list-validator-checkpoint-indices.ts @@ -4,7 +4,8 @@ import { S3Validator } from '../src/agents/aws/validator'; import { deployEnvToSdkEnv } from '../src/config/environment'; import { concurrentMap } from '../src/utils/utils'; -import { getArgs, getEnvironmentConfig, getValidatorsByChain } from './utils'; +import { getArgs, getValidatorsByChain } from './agent-utils'; +import { getEnvironmentConfig } from './core-utils'; async function main() { const { environment } = await getArgs().argv; diff --git a/typescript/infra/scripts/middleware/circle-relayer.ts b/typescript/infra/scripts/middleware/circle-relayer.ts index 0d874357cb..de9288ee0a 100644 --- a/typescript/infra/scripts/middleware/circle-relayer.ts +++ b/typescript/infra/scripts/middleware/circle-relayer.ts @@ -9,11 +9,8 @@ import { import { objFilter } from '@hyperlane-xyz/utils'; import { readJSON, sleep } from '../../src/utils/utils'; -import { - getArgs, - getEnvironmentConfig, - getEnvironmentDirectory, -} from '../utils'; +import { getArgs, getEnvironmentDirectory } from '../agent-utils'; +import { getEnvironmentConfig } from '../core-utils'; async function check() { const { environment } = await getArgs().argv; diff --git a/typescript/infra/scripts/middleware/deploy-relayers.ts b/typescript/infra/scripts/middleware/deploy-relayers.ts index e9643e5068..58a95da84e 100644 --- a/typescript/infra/scripts/middleware/deploy-relayers.ts +++ b/typescript/infra/scripts/middleware/deploy-relayers.ts @@ -4,7 +4,8 @@ import { runLiquidityLayerRelayerHelmCommand, } from '../../src/middleware/liquidity-layer-relayer'; import { HelmCommand } from '../../src/utils/helm'; -import { assertCorrectKubeContext, getConfigsBasedOnArgs } from '../utils'; +import { assertCorrectKubeContext } from '../agent-utils'; +import { getConfigsBasedOnArgs } from '../core-utils'; async function main() { const { agentConfig, envConfig, context } = await getConfigsBasedOnArgs(); diff --git a/typescript/infra/scripts/middleware/portal-relayer.ts b/typescript/infra/scripts/middleware/portal-relayer.ts index bb2d7f9749..09164bb791 100644 --- a/typescript/infra/scripts/middleware/portal-relayer.ts +++ b/typescript/infra/scripts/middleware/portal-relayer.ts @@ -9,11 +9,8 @@ import { error, log } from '@hyperlane-xyz/utils'; import { bridgeAdapterConfigs } from '../../config/environments/testnet4/token-bridge'; import { readJSON, sleep } from '../../src/utils/utils'; -import { - getArgs, - getEnvironmentConfig, - getEnvironmentDirectory, -} from '../utils'; +import { getArgs, getEnvironmentDirectory } from '../agent-utils'; +import { getEnvironmentConfig } from '../core-utils'; async function relayPortalTransfers() { const { environment } = await getArgs().argv; diff --git a/typescript/infra/scripts/module-can-verify.ts b/typescript/infra/scripts/module-can-verify.ts index 7dfb3c70a1..4cffb36bf3 100644 --- a/typescript/infra/scripts/module-can-verify.ts +++ b/typescript/infra/scripts/module-can-verify.ts @@ -3,7 +3,8 @@ import { ProtocolType } from '@hyperlane-xyz/utils'; import { deployEnvToSdkEnv } from '../src/config/environment'; -import { getArgs, getEnvironmentConfig } from './utils'; +import { getArgs } from './agent-utils'; +import { getEnvironmentConfig } from './core-utils'; async function main() { const args = await getArgs().argv; diff --git a/typescript/infra/scripts/print-chain-metadatas.ts b/typescript/infra/scripts/print-chain-metadatas.ts index 7b96c35807..954d676445 100644 --- a/typescript/infra/scripts/print-chain-metadatas.ts +++ b/typescript/infra/scripts/print-chain-metadatas.ts @@ -1,4 +1,5 @@ -import { getArgs, getEnvironmentConfig } from './utils'; +import { getArgs } from './agent-utils'; +import { getEnvironmentConfig } from './core-utils'; // This script exists to print the chain metadata configs for a given environment // so they can easily be copied into the Sealevel tooling. :'( diff --git a/typescript/infra/scripts/print-multisig-ism-config.ts b/typescript/infra/scripts/print-multisig-ism-config.ts index e05aeaf996..3b27ef8b91 100644 --- a/typescript/infra/scripts/print-multisig-ism-config.ts +++ b/typescript/infra/scripts/print-multisig-ism-config.ts @@ -2,7 +2,7 @@ import { AllChains, IsmType } from '@hyperlane-xyz/sdk'; import { multisigIsms } from '../config/multisigIsm'; -import { getArgs, withContext } from './utils'; +import { getArgs, withContext } from './agent-utils'; // This script exists to print the default multisig ISM validator sets for a given environment // so they can easily be copied into the Sealevel tooling. :'( diff --git a/typescript/infra/scripts/rotate-key.ts b/typescript/infra/scripts/rotate-key.ts index 34f942475e..506288dad4 100644 --- a/typescript/infra/scripts/rotate-key.ts +++ b/typescript/infra/scripts/rotate-key.ts @@ -3,7 +3,7 @@ import { getArgs, withContext, withKeyRoleAndChain, -} from './utils'; +} from './agent-utils'; async function rotateKey() { const argv = await withContext(withKeyRoleAndChain(getArgs())).argv; diff --git a/typescript/infra/scripts/safe-delegate.ts b/typescript/infra/scripts/safe-delegate.ts index 22a4d3e149..cc1f22b883 100644 --- a/typescript/infra/scripts/safe-delegate.ts +++ b/typescript/infra/scripts/safe-delegate.ts @@ -8,7 +8,8 @@ import { AllChains } from '@hyperlane-xyz/sdk'; import { getSafeDelegates, getSafeService } from '../src/utils/safe'; -import { getEnvironmentConfig, getArgs as getRootArgs } from './utils'; +import { getArgs as getRootArgs } from './agent-utils'; +import { getEnvironmentConfig } from './core-utils'; function getArgs() { return getRootArgs() diff --git a/typescript/infra/scripts/update-key.ts b/typescript/infra/scripts/update-key.ts index e05013ce88..dab4b279c9 100644 --- a/typescript/infra/scripts/update-key.ts +++ b/typescript/infra/scripts/update-key.ts @@ -3,7 +3,7 @@ import { getArgs, withContext, withKeyRoleAndChain, -} from './utils'; +} from './agent-utils'; async function rotateKey() { const argv = await withKeyRoleAndChain(withContext(getArgs())).argv; diff --git a/typescript/infra/scripts/verify-validators.ts b/typescript/infra/scripts/verify-validators.ts index 2433a6b4f3..a9d288cc23 100644 --- a/typescript/infra/scripts/verify-validators.ts +++ b/typescript/infra/scripts/verify-validators.ts @@ -4,7 +4,8 @@ import { objMap, promiseObjAll } from '@hyperlane-xyz/utils'; import { S3Validator } from '../src/agents/aws/validator'; import { deployEnvToSdkEnv } from '../src/config/environment'; -import { getArgs, getEnvironmentConfig, getValidatorsByChain } from './utils'; +import { getArgs, getValidatorsByChain } from './agent-utils'; +import { getEnvironmentConfig } from './core-utils'; async function main() { const { environment } = await getArgs().argv; diff --git a/typescript/infra/scripts/verify.ts b/typescript/infra/scripts/verify.ts index a2367f087d..a60faf6fde 100644 --- a/typescript/infra/scripts/verify.ts +++ b/typescript/infra/scripts/verify.ts @@ -7,7 +7,8 @@ import { import { fetchGCPSecret } from '../src/utils/gcloud'; import { execCmd, readFileAtPath, readJSONAtPath } from '../src/utils/utils'; -import { assertEnvironment, getArgs, getEnvironmentConfig } from './utils'; +import { assertEnvironment, getArgs } from './agent-utils'; +import { getEnvironmentConfig } from './core-utils'; // Requires https://github.com/crytic/solc-select to be installed and // present in your $PATH. The current solc compiler version should diff --git a/typescript/infra/scripts/warp-routes/deploy-warp-monitor.ts b/typescript/infra/scripts/warp-routes/deploy-warp-monitor.ts index 534267004f..4d903b2c38 100644 --- a/typescript/infra/scripts/warp-routes/deploy-warp-monitor.ts +++ b/typescript/infra/scripts/warp-routes/deploy-warp-monitor.ts @@ -1,12 +1,24 @@ +import yargs from 'yargs'; + import { HelmCommand } from '../../src/utils/helm'; import { runWarpRouteHelmCommand } from './helm'; async function main() { + const { filePath } = await yargs(process.argv.slice(2)) + .alias('f', 'filePath') + .describe( + 'filePath', + 'indicate the filepatch to the warp route yaml file relative to typescript/infra', + ) + .demandOption('filePath') + .string('filePath') + .parse(); + await runWarpRouteHelmCommand( HelmCommand.InstallOrUpgrade, 'mainnet3', - 'neutron', + filePath, ); } diff --git a/typescript/infra/scripts/warp-routes/helm.ts b/typescript/infra/scripts/warp-routes/helm.ts index 170800e003..8a28f85f6e 100644 --- a/typescript/infra/scripts/warp-routes/helm.ts +++ b/typescript/infra/scripts/warp-routes/helm.ts @@ -1,37 +1,38 @@ import { DeployEnvironment } from '../../src/config'; import { HelmCommand, helmifyValues } from '../../src/utils/helm'; import { execCmd } from '../../src/utils/utils'; -import { assertCorrectKubeContext, getEnvironmentConfig } from '../utils'; +import { assertCorrectKubeContext } from '../agent-utils'; +import { getEnvironmentConfig } from '../core-utils'; export async function runWarpRouteHelmCommand( helmCommand: HelmCommand, runEnv: DeployEnvironment, - config: string, + configFilePath: string, ) { const envConfig = getEnvironmentConfig(runEnv); await assertCorrectKubeContext(envConfig); - const values = getWarpRoutesHelmValues(config); - + const values = getWarpRoutesHelmValues(configFilePath); + const releaseName = getHelmReleaseName(configFilePath); return execCmd( - `helm ${helmCommand} ${getHelmReleaseName( - config, - )} ./helm/warp-routes --namespace ${runEnv} ${values.join( + `helm ${helmCommand} ${releaseName} ./helm/warp-routes --namespace ${runEnv} ${values.join( ' ', - )} --set fullnameOverride="${getHelmReleaseName(config)}"`, + )} --set fullnameOverride="${releaseName}"`, ); } function getHelmReleaseName(route: string): string { - return `hyperlane-warp-route-${route}`; + const match = route.match(/\/([^/]+)-deployments\.yaml$/); + const name = match ? match[1] : route; + return `hyperlane-warp-route-${name}`; } -function getWarpRoutesHelmValues(config: string) { +function getWarpRoutesHelmValues(configFilePath: string) { const values = { image: { repository: 'gcr.io/abacus-labs-dev/hyperlane-monorepo', - tag: 'ae8ce44-20231101-012032', + tag: 'a84e439-20240131-224743', }, - config: config, // nautilus or neutron + configFilePath: configFilePath, }; return helmifyValues(values); } diff --git a/typescript/infra/scripts/warp-routes/monitor-warp-routes-balances.ts b/typescript/infra/scripts/warp-routes/monitor-warp-routes-balances.ts index b2631a87cd..dd96d83ec1 100644 --- a/typescript/infra/scripts/warp-routes/monitor-warp-routes-balances.ts +++ b/typescript/infra/scripts/warp-routes/monitor-warp-routes-balances.ts @@ -7,10 +7,13 @@ import { ERC20__factory } from '@hyperlane-xyz/core'; import { ChainMap, ChainName, + CosmNativeTokenAdapter, CwNativeTokenAdapter, MultiProtocolProvider, SealevelHypCollateralAdapter, TokenType, + WarpRouteConfig, + WarpRouteConfigSchema, } from '@hyperlane-xyz/sdk'; import { ProtocolType, @@ -19,12 +22,8 @@ import { promiseObjAll, } from '@hyperlane-xyz/utils'; -import { - WarpTokenConfig, - nautilusList, - neutronList, -} from '../../src/config/grafana_token_config'; import { startMetricsServer } from '../../src/utils/metrics'; +import { readYaml } from '../../src/utils/utils'; const metricsRegister = new Registry(); const warpRouteTokenBalance = new Gauge({ @@ -40,20 +39,36 @@ const warpRouteTokenBalance = new Gauge({ ], }); +export function readWarpRouteConfig(filePath: string) { + const config = readYaml(filePath); + if (!config) throw new Error(`No warp config found at ${filePath}`); + const result = WarpRouteConfigSchema.safeParse(config); + if (!result.success) { + const errorMessages = result.error.issues.map( + (issue: any) => `${issue.path} => ${issue.message}`, + ); + throw new Error(`Invalid warp config:\n ${errorMessages.join('\n')}`); + } + return result.data; +} + async function main(): Promise { - const { checkFrequency, config } = await yargs(process.argv.slice(2)) + const { checkFrequency, filePath } = await yargs(process.argv.slice(2)) .describe('checkFrequency', 'frequency to check balances in ms') .demandOption('checkFrequency') - .alias('l', 'checkFrequency') + .alias('v', 'checkFrequency') // v as in Greek letter nu .number('checkFrequency') - .alias('c', 'config') - .describe('config', 'choose warp token config') - .demandOption('config') - .choices('config', ['neutron', 'nautilus']) + .alias('f', 'filePath') + .describe( + 'filePath', + 'indicate the filepatch to the warp route yaml file relative to typescript/infra', + ) + .demandOption('filePath') + .string('filePath') .parse(); - const tokenList: WarpTokenConfig = - config === 'neutron' ? neutronList : nautilusList; + const tokenConfig: WarpRouteConfig = + readWarpRouteConfig(filePath).data.config; startMetricsServer(metricsRegister); @@ -63,8 +78,8 @@ async function main(): Promise { setInterval(async () => { try { debug('Checking balances'); - const balances = await checkBalance(tokenList, multiProtocolProvider); - updateTokenBalanceMetrics(tokenList, balances); + const balances = await checkBalance(tokenConfig, multiProtocolProvider); + updateTokenBalanceMetrics(tokenConfig, balances); } catch (e) { console.error('Error checking balances', e); } @@ -74,20 +89,18 @@ async function main(): Promise { // TODO: see issue https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/2708 async function checkBalance( - tokenConfig: WarpTokenConfig, + tokenConfig: WarpRouteConfig, multiProtocolProvider: MultiProtocolProvider, ): Promise> { - const output: ChainMap> = objMap( + const output = objMap( tokenConfig, - async (chain: ChainName, token: WarpTokenConfig[ChainName]) => { + async (chain: ChainName, token: WarpRouteConfig[ChainName]) => { switch (token.type) { case TokenType.native: { switch (token.protocolType) { case ProtocolType.Ethereum: { const provider = multiProtocolProvider.getEthersV5Provider(chain); - const nativeBalance = await provider.getBalance( - token.hypNativeAddress, - ); + const nativeBalance = await provider.getBalance(token.hypAddress); return parseFloat( ethers.utils.formatUnits(nativeBalance, token.decimals), ); @@ -95,9 +108,20 @@ async function checkBalance( case ProtocolType.Sealevel: // TODO - solana native return 0; - case ProtocolType.Cosmos: - // TODO - cosmos native - return 0; + case ProtocolType.Cosmos: { + if (!token.ibcDenom) + throw new Error('IBC denom missing for native token'); + const adapter = new CosmNativeTokenAdapter( + chain, + multiProtocolProvider, + {}, + { ibcDenom: token.ibcDenom }, + ); + const tokenBalance = await adapter.getBalance(token.hypAddress); + return parseFloat( + ethers.utils.formatUnits(tokenBalance, token.decimals), + ); + } } break; } @@ -105,12 +129,14 @@ async function checkBalance( switch (token.protocolType) { case ProtocolType.Ethereum: { const provider = multiProtocolProvider.getEthersV5Provider(chain); + if (!token.tokenAddress) + throw new Error('Token address missing for collateral token'); const tokenContract = ERC20__factory.connect( - token.address, + token.tokenAddress, provider, ); const collateralBalance = await tokenContract.balanceOf( - token.hypCollateralAddress, + token.hypAddress, ); return parseFloat( @@ -118,19 +144,21 @@ async function checkBalance( ); } case ProtocolType.Sealevel: { + if (!token.tokenAddress) + throw new Error('Token address missing for synthetic token'); const adapter = new SealevelHypCollateralAdapter( chain, multiProtocolProvider, { - token: token.address, - warpRouter: token.hypCollateralAddress, + token: token.tokenAddress, + warpRouter: token.hypAddress, // Mailbox only required for transfers, using system as placeholder mailbox: SystemProgram.programId.toBase58(), }, - token.isSpl2022, + token?.isSpl2022 ?? false, ); const collateralBalance = ethers.BigNumber.from( - await adapter.getBalance(token.hypCollateralAddress), + await adapter.getBalance(token.hypAddress), ); return parseFloat( ethers.utils.formatUnits(collateralBalance, token.decimals), @@ -141,12 +169,12 @@ async function checkBalance( chain, multiProtocolProvider, { - token: token.address, + token: token.hypAddress, }, - token.address, + token.tokenAddress, ); const collateralBalance = ethers.BigNumber.from( - await adapter.getBalance(token.hypCollateralAddress), + await adapter.getBalance(token.hypAddress), ); return parseFloat( ethers.utils.formatUnits(collateralBalance, token.decimals), @@ -160,7 +188,7 @@ async function checkBalance( case ProtocolType.Ethereum: { const provider = multiProtocolProvider.getEthersV5Provider(chain); const tokenContract = ERC20__factory.connect( - token.hypSyntheticAddress, + token.hypAddress, provider, ); const syntheticBalance = await tokenContract.totalSupply(); @@ -178,36 +206,24 @@ async function checkBalance( break; } } + return 0; }, ); return await promiseObjAll(output); } -function updateTokenBalanceMetrics( - tokenConfig: WarpTokenConfig, +export function updateTokenBalanceMetrics( + tokenConfig: WarpRouteConfig, balances: ChainMap, ) { - objMap(tokenConfig, (chain: ChainName, token: WarpTokenConfig[ChainName]) => { - const tokenAddress = - token.type === TokenType.native - ? ethers.constants.AddressZero - : token.type === TokenType.collateral - ? token.address - : token.hypSyntheticAddress; - const walletAddress = - token.type === TokenType.native - ? token.hypNativeAddress - : token.type === TokenType.collateral - ? token.hypCollateralAddress - : token.hypSyntheticAddress; - + objMap(tokenConfig, (chain: ChainName, token: WarpRouteConfig[ChainName]) => { warpRouteTokenBalance .labels({ chain_name: chain, - token_address: tokenAddress, + token_address: token.tokenAddress ?? ethers.constants.AddressZero, token_name: token.name, - wallet_address: walletAddress, + wallet_address: token.hypAddress, token_type: token.type, }) .set(balances[chain]); diff --git a/typescript/infra/src/agents/aws/key.ts b/typescript/infra/src/agents/aws/key.ts index fb42d10aab..a6c47369e5 100644 --- a/typescript/infra/src/agents/aws/key.ts +++ b/typescript/infra/src/agents/aws/key.ts @@ -16,6 +16,7 @@ import { UpdateAliasCommand, } from '@aws-sdk/client-kms'; import { KmsEthersSigner } from 'aws-kms-ethers-signer'; +import { Debugger, debug } from 'debug'; import { ethers } from 'ethers'; import { AgentSignerKeyType, ChainName } from '@hyperlane-xyz/sdk'; @@ -41,6 +42,7 @@ export class AgentAwsKey extends CloudAgentKey { private client: KMSClient | undefined; private region: string; public remoteKey: RemoteKey = { fetched: false }; + protected logger: Debugger; constructor( agentConfig: AgentContextConfig, @@ -53,16 +55,22 @@ export class AgentAwsKey extends CloudAgentKey { throw new Error('Not configured as AWS'); } this.region = agentConfig.aws.region; + this.logger = debug(`infra:agents:key:aws:${this.identifier}`); } get privateKey(): string { + this.logger( + 'Attempting to access private key, which is unavailable for AWS keys', + ); throw new Error('Private key unavailable for AWS keys'); } async getClient(): Promise { if (this.client) { + this.logger('Returning existing KMSClient instance'); return this.client; } + this.logger('Creating new KMSClient instance'); this.client = new KMSClient({ region: this.region, }); @@ -94,6 +102,7 @@ export class AgentAwsKey extends CloudAgentKey { } async fetch() { + this.logger('Fetching key'); const address = await this.fetchAddressFromAws(); this.remoteKey = { fetched: true, @@ -102,24 +111,28 @@ export class AgentAwsKey extends CloudAgentKey { } async createIfNotExists() { + this.logger('Checking if key exists and creating if not'); const keyId = await this.getId(); // If it doesn't exist, create it if (!keyId) { - // TODO should this be awaited? create is async - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.create(); + this.logger('Key does not exist, creating new key'); + await this.create(); // It can take a moment for the change to propagate await sleep(1000); + } else { + this.logger('Key already exists'); } await this.fetch(); } async delete() { + this.logger('Delete operation called, but not implemented'); throw Error('Not implemented yet'); } // Allows the `userArn` to use the key async putKeyPolicy(userArn: string) { + this.logger(`Putting key policy for user ARN: ${userArn}`); const client = await this.getClient(); const policy = { Version: '2012-10-17', @@ -151,22 +164,29 @@ export class AgentAwsKey extends CloudAgentKey { PolicyName: 'default', // This is the only accepted name }); await client.send(cmd); + this.logger('Key policy put successfully'); } // Gets the Key's ID if it exists, undefined otherwise async getId() { try { + this.logger('Attempting to describe key to get ID'); const keyDescription = await this.describeKey(); - return keyDescription.KeyMetadata?.KeyId; + const keyId = keyDescription.KeyMetadata?.KeyId; + this.logger(`Key ID retrieved: ${keyId}`); + return keyId; } catch (err: any) { if (err.name === 'NotFoundException') { + this.logger('Key not found'); return undefined; } + this.logger(`Error retrieving key ID: ${err}`); throw err; } } create() { + this.logger('Creating new key'); return this._create(false); } @@ -175,6 +195,7 @@ export class AgentAwsKey extends CloudAgentKey { * @returns The address of the new key */ update() { + this.logger('Updating key (creating new key for rotation)'); return this._create(true); } @@ -182,6 +203,7 @@ export class AgentAwsKey extends CloudAgentKey { * Requires update to have been called on this key prior */ async rotate() { + this.logger('Rotating keys'); const canonicalAlias = this.identifier; const newAlias = canonicalAlias + '-new'; const oldAlias = canonicalAlias + '-old'; @@ -226,15 +248,19 @@ export class AgentAwsKey extends CloudAgentKey { // Address should have changed now await this.fetch(); + this.logger('Keys rotated successfully'); } async getSigner( provider?: ethers.providers.Provider, ): Promise { + this.logger('Getting signer'); const keyId = await this.getId(); if (!keyId) { + this.logger('Key ID not defined, cannot get signer'); throw Error('Key ID not defined'); } + this.logger(`Creating KmsEthersSigner with key ID: ${keyId}`); // @ts-ignore We're using a newer version of Provider than // KmsEthersSigner. The return type for getFeeData for this newer // type is a superset of the return type for getFeeData for the older type, @@ -252,12 +278,15 @@ export class AgentAwsKey extends CloudAgentKey { private requireFetched() { if (!this.remoteKey.fetched) { + this.logger('Key has not been fetched yet'); throw new Error('Key not fetched'); } + this.logger('Key has been fetched'); } // Creates a new key and returns its address private async _create(rotate: boolean) { + this.logger(`Creating key with rotation: ${rotate}`); const client = await this.getClient(); const alias = this.identifier; if (!rotate) { @@ -269,6 +298,7 @@ export class AgentAwsKey extends CloudAgentKey { (_) => _.AliasName === alias, ); if (match) { + this.logger(`Alias ${alias} already exists`); throw new Error( `Attempted to create new key but alias ${alias} already exists`, ); @@ -288,6 +318,7 @@ export class AgentAwsKey extends CloudAgentKey { const createResponse = await client.send(command); if (!createResponse.KeyMetadata) { + this.logger('KeyMetadata was not returned when creating the key'); throw new Error('KeyMetadata was not returned when creating the key'); } const keyId = createResponse.KeyMetadata?.KeyId; @@ -298,10 +329,12 @@ export class AgentAwsKey extends CloudAgentKey { ); const address = this.fetchAddressFromAws(keyId); + this.logger(`New key created with ID: ${keyId}`); return address; } private async fetchAddressFromAws(keyId?: string) { + this.logger(`Fetching address from AWS for key ID: ${keyId}`); const client = await this.getClient(); if (!keyId) { @@ -312,10 +345,15 @@ export class AgentAwsKey extends CloudAgentKey { new GetPublicKeyCommand({ KeyId: keyId }), ); - return getEthereumAddress(Buffer.from(publicKeyResponse.PublicKey!)); + const address = getEthereumAddress( + Buffer.from(publicKeyResponse.PublicKey!), + ); + this.logger(`Address fetched: ${address}`); + return address; } private async describeKey(): Promise { + this.logger('Describing key'); const client = await this.getClient(); return client.send( new DescribeKeyCommand({ @@ -325,6 +363,7 @@ export class AgentAwsKey extends CloudAgentKey { } private async getAliases(): Promise { + this.logger('Getting aliases'); const client = await this.getClient(); let aliases: AliasListEntry[] = []; let marker: string | undefined = undefined; @@ -350,6 +389,7 @@ export class AgentAwsKey extends CloudAgentKey { break; } } + this.logger(`Aliases retrieved: ${aliases.length}`); return aliases; } } diff --git a/typescript/infra/src/agents/gcp.ts b/typescript/infra/src/agents/gcp.ts index 4ba314256e..e0fc75874a 100644 --- a/typescript/infra/src/agents/gcp.ts +++ b/typescript/infra/src/agents/gcp.ts @@ -1,9 +1,6 @@ -import { - encodeSecp256k1Pubkey, - pubkeyToAddress, - rawSecp256k1PubkeyToRawAddress, -} from '@cosmjs/amino'; +import { encodeSecp256k1Pubkey, pubkeyToAddress } from '@cosmjs/amino'; import { Keypair } from '@solana/web3.js'; +import { Debugger, debug } from 'debug'; import { Wallet, ethers } from 'ethers'; import { ChainName } from '@hyperlane-xyz/sdk'; @@ -42,6 +39,8 @@ interface FetchedKey { type RemoteKey = UnfetchedKey | FetchedKey; export class AgentGCPKey extends CloudAgentKey { + protected logger: Debugger; + constructor( environment: DeployEnvironment, context: Contexts, @@ -51,18 +50,23 @@ export class AgentGCPKey extends CloudAgentKey { private remoteKey: RemoteKey = { fetched: false }, ) { super(environment, context, role, chainName, index); + this.logger = debug(`infra:agents:key:gcp:${this.identifier}`); } async createIfNotExists() { + this.logger('Checking if key exists and creating if not'); try { await this.fetch(); + this.logger('Key already exists'); } catch (err) { + this.logger('Key does not exist, creating new key'); await this.create(); } } serializeAsAddress() { this.requireFetched(); + this.logger('Serializing key as address'); return { identifier: this.identifier, // @ts-ignore @@ -98,6 +102,7 @@ export class AgentGCPKey extends CloudAgentKey { addressForProtocol(protocol: ProtocolType): string | undefined { this.requireFetched(); + this.logger(`Getting address for protocol: ${protocol}`); switch (protocol) { case ProtocolType.Ethereum: @@ -106,7 +111,7 @@ export class AgentGCPKey extends CloudAgentKey { return Keypair.fromSeed( Buffer.from(strip0x(this.privateKey), 'hex'), ).publicKey.toBase58(); - case ProtocolType.Cosmos: + case ProtocolType.Cosmos: { const compressedPubkey = ethers.utils.computePublicKey( this.privateKey, true, @@ -117,12 +122,15 @@ export class AgentGCPKey extends CloudAgentKey { // TODO support other prefixes? // https://cosmosdrops.io/en/tools/bech32-converter is useful for converting to other prefixes. return pubkeyToAddress(encodedPubkey, 'neutron'); + } default: + this.logger(`Unsupported protocol: ${protocol}`); return undefined; } } async fetch() { + this.logger('Fetching key'); const secret: SecretManagerPersistedKeys = (await fetchGCPSecret( this.identifier, )) as any; @@ -131,25 +139,34 @@ export class AgentGCPKey extends CloudAgentKey { privateKey: secret.privateKey, address: secret.address, }; + this.logger(`Key fetched successfully: ${secret.address}`); } async create() { + this.logger('Creating new key'); this.remoteKey = await this._create(false); + this.logger('Key created successfully'); } async update() { + this.logger('Updating key'); this.remoteKey = await this._create(true); + this.logger('Key updated successfully'); return this.address; } async delete() { + this.logger('Deleting key'); await execCmd(`gcloud secrets delete ${this.identifier} --quiet`); + this.logger('Key deleted successfully'); } async getSigner( provider?: ethers.providers.Provider, ): Promise { + this.logger('Getting signer'); if (!this.remoteKey.fetched) { + this.logger('Key not fetched, fetching now'); await this.fetch(); } return new Wallet(this.privateKey, provider); @@ -157,12 +174,14 @@ export class AgentGCPKey extends CloudAgentKey { private requireFetched() { if (!this.remoteKey.fetched) { + this.logger('Key not fetched, throwing error'); throw new Error("Can't persist without address"); } } // eslint-disable-next-line @typescript-eslint/no-unused-vars private async _create(rotate: boolean) { + this.logger(`Creating key with rotation: ${rotate}`); const wallet = Wallet.createRandom(); const address = await wallet.getAddress(); const identifier = this.identifier; @@ -187,6 +206,7 @@ export class AgentGCPKey extends CloudAgentKey { }), }, ); + this.logger('Key creation data persisted to GCP'); return { fetched: true, diff --git a/typescript/infra/src/agents/key-utils.ts b/typescript/infra/src/agents/key-utils.ts index 11e669d238..47c647bd4b 100644 --- a/typescript/infra/src/agents/key-utils.ts +++ b/typescript/infra/src/agents/key-utils.ts @@ -1,22 +1,42 @@ +import debug from 'debug'; +import fs from 'fs'; +import path from 'path'; + import { ChainMap, ChainName } from '@hyperlane-xyz/sdk'; -import { objMap } from '@hyperlane-xyz/utils'; +import { Address, objMap } from '@hyperlane-xyz/utils'; +import localAWMultisigAddresses from '../../config/aw-multisig.json'; +// AW - Abacus Works import { Contexts } from '../../config/contexts'; -import { getHelloWorldConfig } from '../../scripts/helloworld/utils'; -import { getEnvironmentConfig } from '../../scripts/utils'; +import { helloworld } from '../../config/environments/helloworld'; +import localKathyAddresses from '../../config/kathy.json'; +import localRelayerAddresses from '../../config/relayer.json'; +import { getJustHelloWorldConfig } from '../../scripts/helloworld/utils'; import { AgentContextConfig, DeployEnvironment, RootAgentConfig, } from '../config'; import { Role } from '../roles'; -import { fetchGCPSecret, setGCPSecret } from '../utils/gcloud'; import { execCmd, isEthereumProtocolChain } from '../utils/utils'; import { AgentAwsKey } from './aws/key'; import { AgentGCPKey } from './gcp'; import { CloudAgentKey } from './keys'; +export type LocalRoleAddresses = Record< + DeployEnvironment, + Record +>; +export const relayerAddresses: LocalRoleAddresses = + localRelayerAddresses as LocalRoleAddresses; +export const kathyAddresses: LocalRoleAddresses = + localKathyAddresses as LocalRoleAddresses; +export const awMultisigAddresses: ChainMap<{ validators: Address[] }> = + localAWMultisigAddresses as ChainMap<{ validators: Address[] }>; + +const debugLog = debug('infra:agents:key:utils'); + export interface KeyAsAddress { identifier: string; address: string; @@ -65,9 +85,8 @@ function getRoleKeyMapPerChain( const validators = agentConfig.validators; for (const chainName of agentConfig.contextChainNames.validator) { let chainValidatorKeys = {}; - const validatorCount = - validators?.chains[chainName].validators.length ?? 0; + validators?.chains[chainName]?.validators.length ?? 1; for (let index = 0; index < validatorCount; index++) { const { validator, chainSigner } = getValidatorKeysForChain( agentConfig, @@ -100,9 +119,8 @@ function getRoleKeyMapPerChain( }; const setKathyKeys = () => { - const envConfig = getEnvironmentConfig(agentConfig.runEnv); - const helloWorldConfig = getHelloWorldConfig( - envConfig, + const helloWorldConfig = getJustHelloWorldConfig( + helloworld[agentConfig.runEnv as 'mainnet3' | 'testnet4'], // test doesn't have hello world configs agentConfig.context, ); // Kathy is only needed on chains where the hello world contracts are deployed. @@ -156,6 +174,7 @@ function getRoleKeyMapPerChain( export function getAllCloudAgentKeys( agentConfig: RootAgentConfig, ): Array { + debugLog('Retrieving all cloud agent keys'); const keysPerChain = getRoleKeyMapPerChain(agentConfig); const keysByIdentifier = Object.keys(keysPerChain).reduce( @@ -190,21 +209,22 @@ export function getCloudAgentKey( chainName?: ChainName, index?: number, ): CloudAgentKey { + debugLog(`Retrieving cloud agent key for ${role} on ${chainName}`); switch (role) { case Role.Validator: if (chainName === undefined || index === undefined) { - throw Error(`Must provide chainName and index for validator key`); + throw Error('Must provide chainName and index for validator key'); } // For now just get the validator key, and not the chain signer. return getValidatorKeysForChain(agentConfig, chainName, index).validator; case Role.Relayer: if (chainName === undefined) { - throw Error(`Must provide chainName for relayer key`); + throw Error('Must provide chainName for relayer key'); } return getRelayerKeyForChain(agentConfig, chainName); case Role.Kathy: if (chainName === undefined) { - throw Error(`Must provide chainName for kathy key`); + throw Error('Must provide chainName for kathy key'); } return getKathyKeyForChain(agentConfig, chainName); case Role.Deployer: @@ -223,6 +243,7 @@ export function getRelayerKeyForChain( agentConfig: AgentContextConfig, chainName: ChainName, ): CloudAgentKey { + debugLog(`Retrieving relayer key for ${chainName}`); // If AWS is enabled and the chain is an Ethereum-based chain, we want to use // an AWS key. if (agentConfig.aws && isEthereumProtocolChain(chainName)) { @@ -240,6 +261,7 @@ export function getKathyKeyForChain( agentConfig: AgentContextConfig, chainName: ChainName, ): CloudAgentKey { + debugLog(`Retrieving kathy key for ${chainName}`); // If AWS is enabled and the chain is an Ethereum-based chain, we want to use // an AWS key. if (agentConfig.aws && isEthereumProtocolChain(chainName)) { @@ -252,6 +274,7 @@ export function getKathyKeyForChain( // Returns the deployer key. This is always a GCP key, not chain specific, // and in the Hyperlane context. export function getDeployerKey(agentConfig: AgentContextConfig): CloudAgentKey { + debugLog('Retrieving deployer key'); return new AgentGCPKey(agentConfig.runEnv, Contexts.Hyperlane, Role.Deployer); } @@ -267,6 +290,7 @@ export function getValidatorKeysForChain( validator: CloudAgentKey; chainSigner: CloudAgentKey; } { + debugLog(`Retrieving validator keys for ${chainName}`); const validator = agentConfig.aws ? new AgentAwsKey(agentConfig, Role.Validator, chainName, index) : new AgentGCPKey( @@ -279,15 +303,19 @@ export function getValidatorKeysForChain( // If the chain is Ethereum-based, we can just use the validator key (even if it's AWS-based) // as the chain signer. Otherwise, we need to use a GCP key. - const chainSigner = isEthereumProtocolChain(chainName) - ? validator - : new AgentGCPKey( - agentConfig.runEnv, - agentConfig.context, - Role.Validator, - chainName, - index, - ); + let chainSigner; + if (isEthereumProtocolChain(chainName)) { + chainSigner = validator; + } else { + debugLog(`Retrieving GCP key for ${chainName}, as it is not EVM`); + chainSigner = new AgentGCPKey( + agentConfig.runEnv, + agentConfig.context, + Role.Validator, + chainName, + index, + ); + } return { validator, @@ -302,6 +330,7 @@ export function getValidatorKeysForChain( export async function createAgentKeysIfNotExists( agentConfig: AgentContextConfig, ) { + debugLog('Creating agent keys if none exist'); const keys = getAllCloudAgentKeys(agentConfig); await Promise.all( @@ -310,14 +339,12 @@ export async function createAgentKeysIfNotExists( }), ); - await persistAddresses( - agentConfig.runEnv, - agentConfig.context, - keys.map((key) => key.serializeAsAddress()), - ); + await persistAddressesLocally(agentConfig, keys); + return; } export async function deleteAgentKeys(agentConfig: AgentContextConfig) { + debugLog('Deleting agent keys'); const keys = getAllCloudAgentKeys(agentConfig); await Promise.all(keys.map((key) => key.delete())); await execCmd( @@ -333,48 +360,104 @@ export async function rotateKey( role: Role, chainName: ChainName, ) { + debugLog(`Rotating key for ${role} on ${chainName}`); const key = getCloudAgentKey(agentConfig, role, chainName); await key.update(); - const keyIdentifier = key.identifier; - const addresses = await fetchGCPKeyAddresses( + await persistAddressesLocally(agentConfig, [key]); +} + +async function persistAddressesLocally( + agentConfig: AgentContextConfig, + keys: CloudAgentKey[], +) { + debugLog( + `Persisting addresses to GCP for ${agentConfig.context} context in ${agentConfig.runEnv} environment`, + ); + // recent keys fetched from aws saved to local artifacts + const multisigValidatorKeys: ChainMap<{ validators: Address[] }> = {}; + let relayer, kathy; + for (const key of keys) { + if (key.role === Role.Relayer) { + if (relayer) + throw new Error('More than one Relayer found in gcpCloudAgentKeys'); + relayer = key.address; + } + if (key.role === Role.Kathy) { + if (kathy) + throw new Error('More than one Kathy found in gcpCloudAgentKeys'); + kathy = key.address; + } + if (!key.chainName) continue; + multisigValidatorKeys[key.chainName] ||= { + validators: [], + }; + if (key.chainName) + multisigValidatorKeys[key.chainName].validators.push(key.address); + } + if (!relayer) throw new Error('No Relayer found in awsCloudAgentKeys'); + if (!kathy) throw new Error('No Kathy found in awsCloudAgentKeys'); + await persistRoleAddressesToLocalArtifacts( + Role.Relayer, agentConfig.runEnv, agentConfig.context, + relayer, + relayerAddresses, ); - const filteredAddresses = addresses.filter((_) => { - return _.identifier !== keyIdentifier; - }); - - filteredAddresses.push(key.serializeAsAddress()); - await persistAddresses( + await persistRoleAddressesToLocalArtifacts( + Role.Kathy, agentConfig.runEnv, agentConfig.context, - filteredAddresses, + kathy, + kathyAddresses, ); + await persistValidatorAddressesToLocalArtifacts(multisigValidatorKeys); } -async function persistAddresses( +// non-validator roles +export async function persistRoleAddressesToLocalArtifacts( + role: Role, environment: DeployEnvironment, context: Contexts, - keys: KeyAsAddress[], + updated: Address, + addresses: Record>, ) { - await setGCPSecret( - addressesIdentifier(environment, context), - JSON.stringify(keys), - { - environment, - context, - }, - ); + addresses[environment][context] = updated; + + // Resolve the relative path + const filePath = path.resolve(__dirname, `../../config/${role}.json`); + + fs.writeFileSync(filePath, JSON.stringify(addresses, null, 2)); +} + +// maintaining the multisigIsm schema sans threshold +export async function persistValidatorAddressesToLocalArtifacts( + fetchedValidatorAddresses: ChainMap<{ validators: Address[] }>, +) { + for (const chain of Object.keys(fetchedValidatorAddresses)) { + awMultisigAddresses[chain] = { + validators: fetchedValidatorAddresses[chain].validators, // fresh from aws + }; + } + // Resolve the relative path + const filePath = path.resolve(__dirname, '../../config/aw-multisig.json'); + // Write the updated object back to the file + fs.writeFileSync(filePath, JSON.stringify(awMultisigAddresses, null, 2)); } -async function fetchGCPKeyAddresses( +export function fetchLocalKeyAddresses( + role: Role, environment: DeployEnvironment, context: Contexts, -) { - const addresses = await fetchGCPSecret( - addressesIdentifier(environment, context), +): Address { + // Resolve the relative path + const filePath = path.resolve(__dirname, `../../config/${role}.json`); + const data = fs.readFileSync(filePath, 'utf8'); + const addresses: LocalRoleAddresses = JSON.parse(data); + + debugLog( + `Fetching addresses from GCP for ${context} context in ${environment} environment`, ); - return addresses as KeyAsAddress[]; + return addresses[environment][context]; } function addressesIdentifier( diff --git a/typescript/infra/src/config/grafana_token_config.ts b/typescript/infra/src/config/grafana_token_config.ts deleted file mode 100644 index 9d7e6f4c34..0000000000 --- a/typescript/infra/src/config/grafana_token_config.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { ChainMap, TokenType } from '@hyperlane-xyz/sdk'; -import { ProtocolType } from '@hyperlane-xyz/utils'; - -interface NativeTokenConfig { - symbol: string; - name: string; - type: TokenType.native; - decimals: number; - hypNativeAddress: string; - protocolType: - | ProtocolType.Ethereum - | ProtocolType.Sealevel - | ProtocolType.Cosmos; -} - -interface CollateralTokenConfig { - type: TokenType.collateral; - address: string; - decimals: number; - symbol: string; - name: string; - hypCollateralAddress: string; - isSpl2022?: boolean; - protocolType: - | ProtocolType.Ethereum - | ProtocolType.Sealevel - | ProtocolType.Cosmos; -} - -interface SyntheticTokenConfig { - type: TokenType.synthetic; - hypSyntheticAddress: string; - decimals: number; - symbol: string; - name: string; - protocolType: - | ProtocolType.Ethereum - | ProtocolType.Sealevel - | ProtocolType.Cosmos; -} - -// TODO: migrate and dedupe to SDK from infra and Warp UI -export type WarpTokenConfig = ChainMap< - CollateralTokenConfig | NativeTokenConfig | SyntheticTokenConfig ->; - -/// nautilus configs -export const nautilusList: WarpTokenConfig = { - // bsc - bsc: { - type: TokenType.collateral, - address: '0x37a56cdcD83Dce2868f721De58cB3830C44C6303', - hypCollateralAddress: '0xC27980812E2E66491FD457D488509b7E04144b98', - symbol: 'ZBC', - name: 'Zebec', - decimals: 9, - protocolType: ProtocolType.Ethereum, - }, - - // nautilus - nautilus: { - type: TokenType.native, - hypNativeAddress: '0x4501bBE6e731A4bC5c60C03A77435b2f6d5e9Fe7', - symbol: 'ZBC', - name: 'Zebec', - decimals: 18, - protocolType: ProtocolType.Ethereum, - }, - - // solana - solana: { - type: TokenType.collateral, - address: 'wzbcJyhGhQDLTV1S99apZiiBdE4jmYfbw99saMMdP59', - hypCollateralAddress: 'EJqwFjvVJSAxH8Ur2PYuMfdvoJeutjmH6GkoEFQ4MdSa', - name: 'Zebec', - symbol: 'ZBC', - decimals: 9, - isSpl2022: false, - protocolType: ProtocolType.Sealevel, - }, -}; - -/// neutron configs -export const neutronList: WarpTokenConfig = { - neutron: { - type: TokenType.collateral, - address: - 'ibc/773B4D0A3CD667B2275D5A4A7A2F0909C0BA0F4059C0B9181E680DDF4965DCC7', - hypCollateralAddress: - 'neutron1ch7x3xgpnj62weyes8vfada35zff6z59kt2psqhnx9gjnt2ttqdqtva3pa', - name: 'Celestia', - symbol: 'TIA', - decimals: 6, - protocolType: ProtocolType.Cosmos, - }, - mantapacific: { - type: TokenType.synthetic, - hypSyntheticAddress: '0x6Fae4D9935E2fcb11fC79a64e917fb2BF14DaFaa', - name: 'Celestia', - symbol: 'TIA', - decimals: 6, - protocolType: ProtocolType.Ethereum, - }, -}; diff --git a/typescript/infra/src/deployment/deploy.ts b/typescript/infra/src/deployment/deploy.ts index 43a7f18c63..e0535be4c5 100644 --- a/typescript/infra/src/deployment/deploy.ts +++ b/typescript/infra/src/deployment/deploy.ts @@ -11,7 +11,7 @@ import { } from '@hyperlane-xyz/sdk'; import { objMap, objMerge, promiseObjAll } from '@hyperlane-xyz/utils'; -import { getAgentConfigDirectory } from '../../scripts/utils'; +import { getAgentConfigDirectory } from '../../scripts/agent-utils'; import { DeployEnvironment } from '../config'; import { readJSONAtPath, diff --git a/typescript/infra/src/utils/gcloud.ts b/typescript/infra/src/utils/gcloud.ts index ef2650d357..dde9411ed0 100644 --- a/typescript/infra/src/utils/gcloud.ts +++ b/typescript/infra/src/utils/gcloud.ts @@ -1,3 +1,4 @@ +import debug from 'debug'; import fs from 'fs'; import { rm, writeFile } from 'fs/promises'; @@ -9,6 +10,8 @@ interface IamCondition { expression: string; } +const debugLog = debug('infra:utils:gcloud'); + // Allows secrets to be overridden via environment variables to avoid // gcloud calls. This is particularly useful for running commands in k8s, // where we can use external-secrets to fetch secrets from GCP secret manager, @@ -23,7 +26,7 @@ export async function fetchGCPSecret( const envVarOverride = tryGCPSecretFromEnvVariable(secretName); if (envVarOverride !== undefined) { - console.log( + debugLog( `Using environment variable instead of GCP secret with name ${secretName}`, ); output = envVarOverride; @@ -41,7 +44,7 @@ export async function fetchGCPSecret( // If the environment variable GCP_SECRET_OVERRIDES_ENABLED is `true`, // this will attempt to find an environment variable of the form: -// `GCP_SECRET_OVERRIDE_${gcpSecretName..replaceAll('-', '_').toUpperCase()}` +// `GCP_SECRET_OVERRIDE_${gcpSecretName.replaceAll('-', '_').toUpperCase()}` // If found, it's returned, otherwise, undefined is returned. function tryGCPSecretFromEnvVariable(gcpSecretName: string) { const overridingEnabled = @@ -58,9 +61,12 @@ function tryGCPSecretFromEnvVariable(gcpSecretName: string) { export async function gcpSecretExists(secretName: string) { const fullName = `projects/${await getCurrentProjectNumber()}/secrets/${secretName}`; + debugLog(`Checking if GCP secret exists for ${fullName}`); + const matches = await execCmdAndParseJson( `gcloud secrets list --filter name=${fullName} --format json`, ); + debugLog(`Matches: ${matches.length}`); return matches.length > 0; } @@ -80,10 +86,12 @@ export async function setGCPSecret( await execCmd( `gcloud secrets create ${secretName} --data-file=${fileName} --replication-policy=automatic --labels=${labelString}`, ); + debugLog(`Created new GCP secret for ${secretName}`); } else { await execCmd( `gcloud secrets versions add ${secretName} --data-file=${fileName}`, ); + debugLog(`Added new version to existing GCP secret for ${secretName}`); } await rm(fileName); } @@ -95,6 +103,9 @@ export async function createServiceAccountIfNotExists( let serviceAccountInfo = await getServiceAccountInfo(serviceAccountName); if (!serviceAccountInfo) { serviceAccountInfo = await createServiceAccount(serviceAccountName); + debugLog(`Created new service account with name ${serviceAccountName}`); + } else { + debugLog(`Service account with name ${serviceAccountName} already exists`); } return serviceAccountInfo.email; } @@ -110,6 +121,7 @@ export async function grantServiceAccountRoleIfNotExists( matchedBinding && iamConditionsEqual(condition, matchedBinding.condition) ) { + debugLog(`Service account ${serviceAccountEmail} already has role ${role}`); return; } await execCmd( @@ -119,6 +131,7 @@ export async function grantServiceAccountRoleIfNotExists( : '' }`, ); + debugLog(`Granted role ${role} to service account ${serviceAccountEmail}`); } export async function createServiceAccountKey(serviceAccountEmail: string) { @@ -128,12 +141,14 @@ export async function createServiceAccountKey(serviceAccountEmail: string) { ); const key = JSON.parse(fs.readFileSync(localKeyFile, 'utf8')); fs.rmSync(localKeyFile); + debugLog(`Created new service account key for ${serviceAccountEmail}`); return key; } // The alphanumeric project name / ID export async function getCurrentProject() { const [result] = await execCmd('gcloud config get-value project'); + debugLog(`Current GCP project ID: ${result.trim()}`); return result.trim(); } @@ -150,10 +165,12 @@ async function getIamMemberPolicyBindings(memberEmail: string) { const unprocessedRoles = await execCmdAndParseJson( `gcloud projects get-iam-policy $(gcloud config get-value project) --format "json(bindings)" --flatten="bindings[].members" --filter="bindings.members:${memberEmail}"`, ); - return unprocessedRoles.map((unprocessedRoleObject: any) => ({ + const bindings = unprocessedRoles.map((unprocessedRoleObject: any) => ({ role: unprocessedRoleObject.bindings.role, condition: unprocessedRoleObject.bindings.condition, })); + debugLog(`Retrieved IAM policy bindings for ${memberEmail}`); + return bindings; } async function createServiceAccount(serviceAccountName: string) { @@ -169,8 +186,10 @@ async function getServiceAccountInfo(serviceAccountName: string) { `gcloud iam service-accounts list --format json --filter displayName="${serviceAccountName}"`, ); if (matches.length === 0) { + debugLog(`No service account found with name ${serviceAccountName}`); return undefined; } + debugLog(`Found service account with name ${serviceAccountName}`); return matches[0]; } diff --git a/typescript/infra/src/utils/utils.ts b/typescript/infra/src/utils/utils.ts index 003cfed145..0b6d8a410e 100644 --- a/typescript/infra/src/utils/utils.ts +++ b/typescript/infra/src/utils/utils.ts @@ -4,6 +4,7 @@ import { exec } from 'child_process'; import { ethers } from 'ethers'; import fs from 'fs'; import path from 'path'; +import { parse as yamlParse } from 'yaml'; import { AllChains, @@ -200,6 +201,10 @@ export function readJSON(directory: string, filename: string) { return readJSONAtPath(path.join(directory, filename)); } +export function readYaml(filepath: string): T { + return yamlParse(readFileAtPath(filepath)) as T; +} + export function assertRole(roleStr: string) { const role = roleStr as Role; if (!Object.values(Role).includes(role)) { diff --git a/typescript/infra/tsconfig.json b/typescript/infra/tsconfig.json index 13290d01d9..4a968b40db 100644 --- a/typescript/infra/tsconfig.json +++ b/typescript/infra/tsconfig.json @@ -3,6 +3,7 @@ "outDir": "./dist/", "rootDir": "./", "noUnusedLocals": false, + "resolveJsonModule": true }, "exclude": ["./node_modules/", "./dist/", "./tmp.ts"], "extends": "../tsconfig.json", diff --git a/typescript/sdk/CHANGELOG.md b/typescript/sdk/CHANGELOG.md index e90a804e6d..d4a314049b 100644 --- a/typescript/sdk/CHANGELOG.md +++ b/typescript/sdk/CHANGELOG.md @@ -1,5 +1,12 @@ # @hyperlane-xyz/sdk +## 3.6.2 + +### Patch Changes + +- @hyperlane-xyz/core@3.6.2 +- @hyperlane-xyz/utils@3.6.2 + ## 3.6.1 ### Patch Changes diff --git a/typescript/sdk/package.json b/typescript/sdk/package.json index bf03327d05..7d526909d8 100644 --- a/typescript/sdk/package.json +++ b/typescript/sdk/package.json @@ -1,12 +1,12 @@ { "name": "@hyperlane-xyz/sdk", "description": "The official SDK for the Hyperlane Network", - "version": "3.6.1", + "version": "3.6.2", "dependencies": { "@cosmjs/cosmwasm-stargate": "^0.31.3", "@cosmjs/stargate": "^0.31.3", - "@hyperlane-xyz/core": "3.6.1", - "@hyperlane-xyz/utils": "3.6.1", + "@hyperlane-xyz/core": "3.6.2", + "@hyperlane-xyz/utils": "3.6.2", "@solana/spl-token": "^0.3.8", "@solana/web3.js": "^1.78.0", "@types/coingecko-api": "^1.0.10", diff --git a/typescript/sdk/src/consts/chainMetadata.ts b/typescript/sdk/src/consts/chainMetadata.ts index 7a8a469b66..458c2e6e48 100644 --- a/typescript/sdk/src/consts/chainMetadata.ts +++ b/typescript/sdk/src/consts/chainMetadata.ts @@ -419,6 +419,58 @@ export const gnosis: ChainMetadata = { ], }; +export const inevm: ChainMetadata = { + blockExplorers: [ + { + apiUrl: 'https://inevm.calderaexplorer.xyz/api', + family: ExplorerFamily.Blockscout, + name: 'Caldera inEVM Explorer', + url: 'https://inevm.calderaexplorer.xyz', + }, + ], + blocks: { + confirmations: 1, + estimateBlockTime: 3, + reorgPeriod: 0, + }, + chainId: 2525, + displayName: 'Injective EVM', + displayNameShort: 'inEVM', + domainId: 2525, + name: Chains.inevm, + nativeToken: { + decimals: 18, + name: 'Injective', + symbol: 'INJ', + }, + protocol: ProtocolType.Ethereum, + rpcUrls: [{ http: 'https://inevm.calderachain.xyz/http' }], +}; + +export const injective: ChainMetadata = { + bech32Prefix: 'inj', + blockExplorers: [], + blocks: { + confirmations: 1, + estimateBlockTime: 3, + reorgPeriod: 1, + }, + chainId: 'injective-1', + displayName: 'Injective', + domainId: 6909546, + grpcUrls: [{ http: 'sentry.chain.grpc.injective.network:443' }], + name: Chains.injective, + nativeToken: { + decimals: 18, + name: 'Injective', + symbol: 'INJ', + }, + protocol: ProtocolType.Cosmos, + restUrls: [{ http: 'https://sentry.lcd.injective.network:443' }], + rpcUrls: [{ http: 'https://sentry.tm.injective.network:443' }], + slip44: 118, +}; + export const lineagoerli: ChainMetadata = { blockExplorers: [ { @@ -853,6 +905,7 @@ export const sepolia: ChainMetadata = { nativeToken: etherToken, protocol: ProtocolType.Ethereum, rpcUrls: [ + { http: 'https://ethereum-sepolia.publicnode.com' }, { http: 'https://ethereum-sepolia.blockpi.network/v1/rpc/public' }, { http: 'https://rpc.sepolia.org' }, ], @@ -1036,6 +1089,9 @@ export const viction: ChainMetadata = { }, protocol: ProtocolType.Ethereum, rpcUrls: [ + { + http: 'https://rpc.tomochain.com/', + }, { http: 'https://viction.blockpi.network/v1/rpc/public', }, @@ -1064,6 +1120,8 @@ export const chainMetadata: ChainMap = { fuji, gnosis, goerli, + inevm, + injective, lineagoerli, mantapacific, moonbasealpha, diff --git a/typescript/sdk/src/consts/chains.ts b/typescript/sdk/src/consts/chains.ts index 1711c1d107..09b3749811 100644 --- a/typescript/sdk/src/consts/chains.ts +++ b/typescript/sdk/src/consts/chains.ts @@ -17,6 +17,8 @@ export enum Chains { fuji = 'fuji', gnosis = 'gnosis', goerli = 'goerli', + inevm = 'inevm', + injective = 'injective', lineagoerli = 'lineagoerli', mantapacific = 'mantapacific', moonbasealpha = 'moonbasealpha', @@ -71,6 +73,8 @@ export const Mainnets: Array = [ Chains.base, Chains.scroll, Chains.polygonzkevm, + Chains.injective, + Chains.inevm, Chains.viction, // Chains.solana, ]; diff --git a/typescript/sdk/src/consts/environments/mainnet.json b/typescript/sdk/src/consts/environments/mainnet.json index 041af75f41..82f22b523a 100644 --- a/typescript/sdk/src/consts/environments/mainnet.json +++ b/typescript/sdk/src/consts/environments/mainnet.json @@ -257,6 +257,26 @@ "fallbackRoutingHook": "0xD1E267d2d7876e97E217BfE61c34AB50FEF52807", "interchainSecurityModule": "0xDEed16fe4b1c9b2a93483EDFf34C77A9b57D31Ff" }, + "inevm": { + "merkleRootMultisigIsmFactory": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", + "messageIdMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", + "aggregationIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", + "aggregationHookFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", + "routingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", + "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", + "proxyAdmin": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "storageGasOracle": "0x6119E37Bd66406A1Db74920aC79C15fB8411Ba76", + "interchainGasPaymaster": "0x19dc38aeae620380430C200a6E990D5Af5480117", + "merkleTreeHook": "0x0972954923a1e2b2aAb04Fa0c4a0797e5989Cd65", + "aggregationHook": "0xe0dDb5dE7D52918237cC1Ae131F29dcAbcb0F62B", + "protocolFee": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", + "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "validatorAnnounce": "0x15ab173bDB6832f9b64276bA128659b0eD77730B", + "interchainSecurityModule": "0x3052aD50De54aAAc5D364d80bBE681d29e924597", + "pausableIsm": "0x6Fae4D9935E2fcb11fC79a64e917fb2BF14DaFaa", + "staticAggregationIsm": "0x3052aD50De54aAAc5D364d80bBE681d29e924597", + "pausableHook": "0xBDa330Ea8F3005C421C8088e638fBB64fA71b9e0" + }, "viction": { "merkleRootMultisigIsmFactory": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", "messageIdMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", diff --git a/typescript/sdk/src/consts/multisigIsm.ts b/typescript/sdk/src/consts/multisigIsm.ts index fb507c693e..2d2bde114f 100644 --- a/typescript/sdk/src/consts/multisigIsm.ts +++ b/typescript/sdk/src/consts/multisigIsm.ts @@ -143,6 +143,24 @@ export const defaultMultisigConfigs: ChainMap = { ], }, + inevm: { + threshold: 2, + validators: [ + '0xf9e35ee88e4448a3673b4676a4e153e3584a08eb', + '0xae3e6bb6b3ece1c425aa6f47adc8cb0453c1f9a2', + '0xd98c9522cd9d3e3e00bee05ff76c34b91b266ec3', + ], + }, + + injective: { + threshold: 2, + validators: [ + '0xbfb8911b72cfb138c7ce517c57d9c691535dc517', + '0x6faa139c33a7e6f53cb101f6b2ae392298283ed2', + '0x0115e3a66820fb99da30d30e2ce52a453ba99d92', + ], + }, + lineagoerli: { threshold: 2, validators: [ diff --git a/typescript/sdk/src/core/HyperlaneCore.ts b/typescript/sdk/src/core/HyperlaneCore.ts index 2d7f928dcc..b373874340 100644 --- a/typescript/sdk/src/core/HyperlaneCore.ts +++ b/typescript/sdk/src/core/HyperlaneCore.ts @@ -5,13 +5,16 @@ import { Mailbox__factory } from '@hyperlane-xyz/core'; import { Address, AddressBytes32, + ProtocolType, messageId, + objFilter, objMap, parseMessage, pollAsync, } from '@hyperlane-xyz/utils'; import { HyperlaneApp } from '../app/HyperlaneApp'; +import { chainMetadata } from '../consts/chainMetadata'; import { HyperlaneEnvironment, hyperlaneEnvironments, @@ -52,11 +55,19 @@ export class HyperlaneCore extends HyperlaneApp { getRouterConfig = ( owners: Address | ChainMap, - ): ChainMap => - objMap(this.contractsMap, (chain, contracts) => ({ + ): ChainMap => { + // get config + const config = objMap(this.contractsMap, (chain, contracts) => ({ mailbox: contracts.mailbox.address, owner: typeof owners === 'string' ? owners : owners[chain].owner, })); + // filter for EVM chains + return objFilter( + config, + (chainName, _): _ is RouterConfig => + chainMetadata[chainName].protocol === ProtocolType.Ethereum, + ); + }; quoteGasPayment = ( origin: ChainName, diff --git a/typescript/sdk/src/index.ts b/typescript/sdk/src/index.ts index d9dc0501e2..87b03652f2 100644 --- a/typescript/sdk/src/index.ts +++ b/typescript/sdk/src/index.ts @@ -185,8 +185,8 @@ export { RpcUrlSchema, getChainIdNumber, getDomainId, - isValidChainMetadata, getReorgPeriod, + isValidChainMetadata, } from './metadata/chainMetadataTypes'; export { ZHash } from './metadata/customZodTypes'; export { @@ -194,6 +194,10 @@ export { HyperlaneDeploymentArtifactsSchema, } from './metadata/deploymentArtifacts'; export { MatchingList } from './metadata/matchingList'; +export { + WarpRouteConfig, + WarpRouteConfigSchema, +} from './metadata/warpRouteConfig'; export { InterchainAccount } from './middleware/account/InterchainAccount'; export { InterchainAccountChecker } from './middleware/account/InterchainAccountChecker'; export { diff --git a/typescript/sdk/src/metadata/agentConfig.ts b/typescript/sdk/src/metadata/agentConfig.ts index e7133b4c4a..5aa89687ff 100644 --- a/typescript/sdk/src/metadata/agentConfig.ts +++ b/typescript/sdk/src/metadata/agentConfig.ts @@ -124,7 +124,7 @@ export const AgentChainMetadataSchema = ChainMetadataSchemaObject.merge( .string() .optional() .describe( - 'Specify a comma seperated list of custom RPC URLs to use for this chain. If not specified, the default RPC urls will be used.', + 'Specify a comma separated list of custom RPC URLs to use for this chain. If not specified, the default RPC urls will be used.', ), rpcConsensusType: z .nativeEnum(RpcConsensusType) diff --git a/typescript/sdk/src/metadata/chainMetadataTypes.ts b/typescript/sdk/src/metadata/chainMetadataTypes.ts index b13cea0349..565a151af0 100644 --- a/typescript/sdk/src/metadata/chainMetadataTypes.ts +++ b/typescript/sdk/src/metadata/chainMetadataTypes.ts @@ -115,6 +115,12 @@ export const ChainMetadataSchemaObject = z.object({ .array(RpcUrlSchema) .describe('For cosmos chains only, a list of gRPC API URLs') .optional(), + customGrpcUrls: z + .string() + .optional() + .describe( + 'Specify a comma separated list of custom GRPC URLs to use for this chain. If not specified, the default GRPC urls will be used.', + ), blockExplorers: z .array( z.object({ diff --git a/typescript/sdk/src/metadata/warpRouteConfig.ts b/typescript/sdk/src/metadata/warpRouteConfig.ts new file mode 100644 index 0000000000..e8028c8155 --- /dev/null +++ b/typescript/sdk/src/metadata/warpRouteConfig.ts @@ -0,0 +1,27 @@ +import { z } from 'zod'; + +import { ProtocolType } from '@hyperlane-xyz/utils'; + +import { TokenType } from '../token/config'; +import { ChainMap } from '../types'; + +const TokenConfigSchema = z.object({ + protocolType: z.nativeEnum(ProtocolType), + type: z.nativeEnum(TokenType), + hypAddress: z.string(), // HypERC20Collateral, HypERC20Synthetic, HypNativeToken address + tokenAddress: z.string().optional(), // external token address needed for collateral type eg tokenAddress.balanceOf(hypAddress) + name: z.string(), + symbol: z.string(), + decimals: z.number(), + isSpl2022: z.boolean().optional(), // Solana Program Library 2022, sealevel specific + ibcDenom: z.string().optional(), // IBC denom for cosmos native token +}); + +export const WarpRouteConfigSchema = z.object({ + description: z.string().optional(), + timeStamp: z.string().optional(), // can make it non-optional if we make it part of the warp route deployment progress + deployer: z.string().optional(), + data: z.object({ config: z.record(TokenConfigSchema) }), +}); + +export type WarpRouteConfig = ChainMap>; diff --git a/typescript/sdk/src/providers/MultiProvider.ts b/typescript/sdk/src/providers/MultiProvider.ts index 7e7d367d94..0b1af53309 100644 --- a/typescript/sdk/src/providers/MultiProvider.ts +++ b/typescript/sdk/src/providers/MultiProvider.ts @@ -289,9 +289,22 @@ export class MultiProvider extends ChainMetadataManager { factory: F, params: Parameters, ): Promise>> { + // setup contract factory const overrides = this.getTransactionOverrides(chainNameOrId); const signer = this.getSigner(chainNameOrId); - const contract = await factory.connect(signer).deploy(...params, overrides); + const contractFactory = await factory.connect(signer); + + // estimate gas + const deployTx = contractFactory.getDeployTransaction(...params, overrides); + const gasEstimated = await signer.estimateGas(deployTx); + + // deploy with 10% buffer on gas limit + const contract = await contractFactory.deploy(...params, { + ...overrides, + gasLimit: gasEstimated.add(gasEstimated.div(10)), // 10% buffer + }); + + // wait for deploy tx to be confirmed await this.handleTx(chainNameOrId, contract.deployTransaction); return contract as Awaited>; } diff --git a/typescript/sdk/src/providers/SmartProvider/SmartProvider.ts b/typescript/sdk/src/providers/SmartProvider/SmartProvider.ts index 227dc3827a..9f70fba5c1 100644 --- a/typescript/sdk/src/providers/SmartProvider/SmartProvider.ts +++ b/typescript/sdk/src/providers/SmartProvider/SmartProvider.ts @@ -1,4 +1,4 @@ -import debug from 'debug'; +import debug, { Debugger } from 'debug'; import { providers } from 'ethers'; import { @@ -36,7 +36,8 @@ export class HyperlaneSmartProvider extends providers.BaseProvider implements IProviderMethods { - protected readonly logger = debug('hyperlane:SmartProvider'); + protected logger: Debugger; + // TODO also support blockscout here public readonly explorerProviders: HyperlaneEtherscanProvider[]; public readonly rpcProviders: HyperlaneJsonRpcProvider[]; @@ -52,6 +53,8 @@ export class HyperlaneSmartProvider super(network); const supportedMethods = new Set(); + this.logger = debug(`hyperlane:SmartProvider:${this.network.chainId}`); + if (!rpcUrls?.length && !blockExplorers?.length) throw new Error('At least one RPC URL or block explorer is required'); diff --git a/typescript/sdk/tsconfig.json b/typescript/sdk/tsconfig.json index 9bf7368a74..72d5a04621 100644 --- a/typescript/sdk/tsconfig.json +++ b/typescript/sdk/tsconfig.json @@ -2,7 +2,8 @@ "extends": "../tsconfig.json", "compilerOptions": { "outDir": "./dist/", - "rootDir": "./src/" + "rootDir": "./src/", + "resolveJsonModule": true }, - "include": ["./src/**/*.ts", "./src/*.d.ts"], + "include": ["./src/**/*.ts", "./src/*.d.ts"] } diff --git a/typescript/utils/CHANGELOG.md b/typescript/utils/CHANGELOG.md index bacd69aaf4..57f4c424d6 100644 --- a/typescript/utils/CHANGELOG.md +++ b/typescript/utils/CHANGELOG.md @@ -1,5 +1,7 @@ # @hyperlane-xyz/utils +## 3.6.2 + ## 3.6.1 ### Patch Changes diff --git a/typescript/utils/package.json b/typescript/utils/package.json index 9f88525122..a578e30cb8 100644 --- a/typescript/utils/package.json +++ b/typescript/utils/package.json @@ -1,7 +1,7 @@ { "name": "@hyperlane-xyz/utils", "description": "General utilities and types for the Hyperlane network", - "version": "3.6.1", + "version": "3.6.2", "dependencies": { "@cosmjs/encoding": "^0.31.3", "@solana/web3.js": "^1.78.0", diff --git a/yarn.lock b/yarn.lock index 34d6186d52..e2c15d053e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4235,8 +4235,8 @@ __metadata: version: 0.0.0-use.local resolution: "@hyperlane-xyz/cli@workspace:typescript/cli" dependencies: - "@hyperlane-xyz/sdk": "npm:3.6.1" - "@hyperlane-xyz/utils": "npm:3.6.1" + "@hyperlane-xyz/sdk": "npm:3.6.2" + "@hyperlane-xyz/utils": "npm:3.6.2" "@inquirer/prompts": "npm:^3.0.0" "@types/mocha": "npm:^10.0.1" "@types/node": "npm:^18.14.5" @@ -4261,12 +4261,12 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/core@npm:3.6.1, @hyperlane-xyz/core@workspace:solidity": +"@hyperlane-xyz/core@npm:3.6.2, @hyperlane-xyz/core@workspace:solidity": version: 0.0.0-use.local resolution: "@hyperlane-xyz/core@workspace:solidity" dependencies: "@eth-optimism/contracts": "npm:^0.6.0" - "@hyperlane-xyz/utils": "npm:3.6.1" + "@hyperlane-xyz/utils": "npm:3.6.2" "@nomiclabs/hardhat-ethers": "npm:^2.2.1" "@nomiclabs/hardhat-waffle": "npm:^2.0.6" "@openzeppelin/contracts": "npm:^4.9.3" @@ -4293,12 +4293,12 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/helloworld@npm:3.6.1, @hyperlane-xyz/helloworld@workspace:typescript/helloworld": +"@hyperlane-xyz/helloworld@npm:3.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:3.6.1" - "@hyperlane-xyz/sdk": "npm:3.6.1" + "@hyperlane-xyz/core": "npm:3.6.2" + "@hyperlane-xyz/sdk": "npm:3.6.2" "@nomiclabs/hardhat-ethers": "npm:^2.2.1" "@nomiclabs/hardhat-waffle": "npm:^2.0.6" "@openzeppelin/contracts-upgradeable": "npm:^4.9.3" @@ -4343,9 +4343,9 @@ __metadata: "@ethersproject/experimental": "npm:^5.7.0" "@ethersproject/hardware-wallets": "npm:^5.7.0" "@ethersproject/providers": "npm:^5.7.2" - "@hyperlane-xyz/helloworld": "npm:3.6.1" - "@hyperlane-xyz/sdk": "npm:3.6.1" - "@hyperlane-xyz/utils": "npm:3.6.1" + "@hyperlane-xyz/helloworld": "npm:3.6.2" + "@hyperlane-xyz/sdk": "npm:3.6.2" + "@hyperlane-xyz/utils": "npm:3.6.2" "@nomiclabs/hardhat-ethers": "npm:^2.2.1" "@nomiclabs/hardhat-etherscan": "npm:^3.0.3" "@nomiclabs/hardhat-waffle": "npm:^2.0.6" @@ -4393,14 +4393,14 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/sdk@npm:3.6.1, @hyperlane-xyz/sdk@workspace:typescript/sdk": +"@hyperlane-xyz/sdk@npm:3.6.2, @hyperlane-xyz/sdk@workspace:typescript/sdk": version: 0.0.0-use.local resolution: "@hyperlane-xyz/sdk@workspace:typescript/sdk" dependencies: "@cosmjs/cosmwasm-stargate": "npm:^0.31.3" "@cosmjs/stargate": "npm:^0.31.3" - "@hyperlane-xyz/core": "npm:3.6.1" - "@hyperlane-xyz/utils": "npm:3.6.1" + "@hyperlane-xyz/core": "npm:3.6.2" + "@hyperlane-xyz/utils": "npm:3.6.2" "@nomiclabs/hardhat-ethers": "npm:^2.2.1" "@nomiclabs/hardhat-waffle": "npm:^2.0.6" "@solana/spl-token": "npm:^0.3.8" @@ -4437,7 +4437,7 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/utils@npm:3.6.1, @hyperlane-xyz/utils@workspace:typescript/utils": +"@hyperlane-xyz/utils@npm:3.6.2, @hyperlane-xyz/utils@workspace:typescript/utils": version: 0.0.0-use.local resolution: "@hyperlane-xyz/utils@workspace:typescript/utils" dependencies: