diff --git a/.changeset/many-rice-wave.md b/.changeset/many-rice-wave.md deleted file mode 100644 index 46b8eabc3..000000000 --- a/.changeset/many-rice-wave.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"@hyperlane-xyz/cli": patch -"@hyperlane-xyz/helloworld": patch -"@hyperlane-xyz/infra": patch ---- - -fix: minor change was breaking in registry export diff --git a/.changeset/neat-ducks-own.md b/.changeset/neat-ducks-own.md deleted file mode 100644 index af030bd5e..000000000 --- a/.changeset/neat-ducks-own.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperlane-xyz/cli': minor ---- - -Add hyperlane validator address command to retrieve validator address from AWS diff --git a/.changeset/nice-rivers-own.md b/.changeset/nice-rivers-own.md deleted file mode 100644 index 8000edf81..000000000 --- a/.changeset/nice-rivers-own.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@hyperlane-xyz/infra': minor -'@hyperlane-xyz/cli': minor -'@hyperlane-xyz/sdk': minor ---- - -Implement multi collateral warp routes diff --git a/.changeset/perfect-seahorses-add.md b/.changeset/perfect-seahorses-add.md deleted file mode 100644 index 372ca45e8..000000000 --- a/.changeset/perfect-seahorses-add.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@hyperlane-xyz/cli': minor -'@hyperlane-xyz/sdk': minor -'@hyperlane-xyz/core': minor ---- - -Support xERC20 and xERC20 Lockbox in SDK and CLI diff --git a/.changeset/sweet-pandas-brush.md b/.changeset/sweet-pandas-brush.md new file mode 100644 index 000000000..74e05a094 --- /dev/null +++ b/.changeset/sweet-pandas-brush.md @@ -0,0 +1,5 @@ +--- +"@hyperlane-xyz/core": patch +--- + +fix: make XERC20 and XERC20 Lockbox proxy-able diff --git a/.changeset/witty-vans-return.md b/.changeset/witty-vans-return.md deleted file mode 100644 index 2ed82ae27..000000000 --- a/.changeset/witty-vans-return.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@hyperlane-xyz/infra': minor -'@hyperlane-xyz/utils': minor -'@hyperlane-xyz/sdk': minor ---- - -Implement metadata builder fetching from message diff --git a/.github/workflows/rust-skipped.yml b/.github/workflows/rust-skipped.yml index b6e6c51cd..a854837a0 100644 --- a/.github/workflows/rust-skipped.yml +++ b/.github/workflows/rust-skipped.yml @@ -9,6 +9,8 @@ on: paths-ignore: - 'rust/**' - .github/workflows/rust.yml + # Support for merge queues + merge_group: env: CARGO_TERM_COLOR: always @@ -16,12 +18,10 @@ env: jobs: test-rs: runs-on: ubuntu-latest - steps: - run: 'echo "No test required" ' lint-rs: runs-on: ubuntu-latest - steps: - run: 'echo "No lint required" ' diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 48caa1f77..e91bed49e 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -6,7 +6,9 @@ on: paths: - 'rust/**' - .github/workflows/rust.yml - + - '!*.md' + # Support for merge queues + merge_group: # Allows you to run this workflow manually from the Actions tab workflow_dispatch: diff --git a/.github/workflows/test-skipped.yml b/.github/workflows/test-skipped.yml new file mode 100644 index 000000000..3cff77784 --- /dev/null +++ b/.github/workflows/test-skipped.yml @@ -0,0 +1,109 @@ +name: test + +on: + push: + branches: [main] + pull_request: + branches: + - '*' + paths: + - '*.md' + - '!**/*' + merge_group: + +concurrency: + group: e2e-${{ github.ref }} + cancel-in-progress: ${{ github.ref_name != 'main' }} + +jobs: + yarn-install: + runs-on: ubuntu-latest + steps: + - name: Instant pass + run: echo "yarn-install job passed" + + yarn-build: + runs-on: ubuntu-latest + steps: + - name: Instant pass + run: echo "yarn-build job passed" + + checkout-registry: + runs-on: ubuntu-latest + steps: + - name: Instant pass + run: echo "checkout-registry job passed" + + lint-prettier: + runs-on: ubuntu-latest + steps: + - name: Instant pass + run: echo "lint-prettier job passed" + + yarn-test: + runs-on: ubuntu-latest + steps: + - name: Instant pass + run: echo "yarn-test job passed" + + agent-configs: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + environment: [mainnet3, testnet4] + steps: + - name: Instant pass + run: echo "agent-configs job passed" + + e2e-matrix: + runs-on: ubuntu-latest + if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.base_ref == 'main') || github.event_name == 'merge_group' + strategy: + matrix: + e2e-type: [cosmwasm, non-cosmwasm] + steps: + - name: Instant pass + run: echo "e2e-matrix job passed" + + e2e: + runs-on: ubuntu-latest + if: always() + steps: + - name: Instant pass + run: echo "e2e job passed" + + cli-e2e: + runs-on: ubuntu-latest + if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.base_ref == 'main') || github.event_name == 'merge_group' + strategy: + matrix: + include: + - test-type: preset_hook_enabled + - test-type: configure_hook_enabled + - test-type: pi_with_core_chain + steps: + - name: Instant pass + run: echo "cli-e2e job passed" + + env-test: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + environment: [mainnet3] + chain: [ethereum, arbitrum, optimism, inevm, viction] + module: [core, igp] + include: + - environment: testnet4 + chain: sepolia + module: core + steps: + - name: Instant pass + run: echo "env-test job passed" + + coverage: + runs-on: ubuntu-latest + steps: + - name: Instant pass + run: echo "coverage job passed" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b6bb18d5f..664383cc2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,6 +7,10 @@ on: pull_request: branches: - '*' # run against all branches + paths-ignore: + - '*.md' + # Support for merge queues + merge_group: # Allows you to run this workflow manually from the Actions tab workflow_dispatch: @@ -224,7 +228,7 @@ jobs: e2e-matrix: runs-on: larger-runner - if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.base_ref == 'main') + if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.base_ref == 'main') || github.event_name == 'merge_group' needs: [yarn-build, checkout-registry] strategy: matrix: @@ -325,7 +329,7 @@ jobs: cli-e2e: runs-on: larger-runner - if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.base_ref == 'main') + if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.base_ref == 'main') || github.event_name == 'merge_group' needs: [yarn-build, checkout-registry] strategy: matrix: diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index a88580d40..b7e03e675 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -9,10 +9,10 @@ This CoC applies to all members of the Hyperlane Network's community including, **Code** 1. Never harass or bully anyone. Not verbally, not physically, not sexually. Harassment will not be tolerated. -2. Never discrimnate on the basis of personal characteristics or group membership. +2. Never discriminate on the basis of personal characteristics or group membership. 3. Treat your fellow contributors with respect, fairness, and professionalism, especially in situations of high pressure. -4. Seek, offer, and accept objective critism of yours and others work, strive to acknowledge the contributions of others. -5. Be transparent and honest about your qualifications and any potential conflicts of interest. Transparency is a key tenant of the Hyperlane project and we expect it from all contributors. +4. Seek, offer, and accept objective criticism of yours and others work, strive to acknowledge the contributions of others. +5. Be transparent and honest about your qualifications and any potential conflicts of interest. Transparency is a key tenet of the Hyperlane project and we expect it from all contributors. 6. Bring an open and curious mind, the Hyperlane project is designed to enable developers to express their curiosity, experiment, and build things we couldn't have imagined ourselves. 7. Stay on track - Do your best to avoid off-topic discussion and make sure you are posting to the correct channel and repositories. Distractions are costly and it is far too easy for work to go off track. 8. Step down properly - Think of your fellow contributors when you step down from the project. Contributors of open-source projects come and go. It is crucial that when you leave the project or reduce your contribution significantly you do so in a way that minimizes disruption and keeps continuity in mind. Concretely this means telling your fellow contributors you are leaving and taking the proper steps to enable a smooth transition for other contributors to pick up where you left off. diff --git a/README.md b/README.md index 04551feee..7ff7af932 100644 --- a/README.md +++ b/README.md @@ -103,3 +103,9 @@ See [`rust/README.md`](rust/README.md) - Create a summary of change highlights - Create a "breaking changes" section with any changes required - Deploy agents with the new image tag (if it makes sense to) + +### Releasing packages to NPM + +We use [changesets](https://github.com/changesets/changesets) to release to NPM. You can use the `release` script in `package.json` to publish. + +For an alpha or beta version, follow the directions [here](https://github.com/changesets/changesets/blob/main/docs/prereleases.md). diff --git a/funding.json b/funding.json new file mode 100644 index 000000000..7eca8c578 --- /dev/null +++ b/funding.json @@ -0,0 +1,5 @@ +{ + "opRetro": { + "projectId": "0xa47182d330bd0c5c69b1418462f3f742099138f09bff057189cdd19676a6acd1" + } +} diff --git a/rust/.vscode/extensions.json b/rust/.vscode/extensions.json index e38df3a9f..c8e7623ea 100644 --- a/rust/.vscode/extensions.json +++ b/rust/.vscode/extensions.json @@ -4,7 +4,7 @@ // List of extensions which should be recommended for users of this workspace. "recommendations": [ - "panicbit.cargo", + "rust-lang.rust-analyzer", "tamasfe.even-better-toml", "rust-lang.rust-analyzer", ], diff --git a/rust/Cargo.lock b/rust/Cargo.lock index e38be6559..e5412da29 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -7275,7 +7275,9 @@ dependencies = [ "macro_rules_attribute", "maplit", "nix 0.26.4", + "once_cell", "regex", + "relayer", "ripemd", "serde", "serde_json", diff --git a/rust/agents/relayer/Cargo.toml b/rust/agents/relayer/Cargo.toml index 2df8f54d9..cf35092f7 100644 --- a/rust/agents/relayer/Cargo.toml +++ b/rust/agents/relayer/Cargo.toml @@ -38,7 +38,7 @@ tracing-futures.workspace = true tracing.workspace = true hyperlane-core = { path = "../../hyperlane-core", features = ["agent", "async"] } -hyperlane-base = { path = "../../hyperlane-base" } +hyperlane-base = { path = "../../hyperlane-base", features = ["test-utils"] } hyperlane-ethereum = { path = "../../chains/hyperlane-ethereum" } [dev-dependencies] diff --git a/rust/agents/relayer/src/lib.rs b/rust/agents/relayer/src/lib.rs new file mode 100644 index 000000000..62b896d62 --- /dev/null +++ b/rust/agents/relayer/src/lib.rs @@ -0,0 +1,10 @@ +mod merkle_tree; +mod msg; +mod processor; +mod prover; +mod relayer; +mod server; +mod settings; + +pub use msg::GAS_EXPENDITURE_LOG_MESSAGE; +pub use relayer::*; diff --git a/rust/agents/relayer/src/main.rs b/rust/agents/relayer/src/main.rs index 1223702f8..7d085f529 100644 --- a/rust/agents/relayer/src/main.rs +++ b/rust/agents/relayer/src/main.rs @@ -11,15 +11,7 @@ use eyre::Result; use hyperlane_base::agent_main; -use crate::relayer::Relayer; - -mod merkle_tree; -mod msg; -mod processor; -mod prover; -mod relayer; -mod server; -mod settings; +use relayer::Relayer; #[tokio::main(flavor = "multi_thread", worker_threads = 20)] async fn main() -> Result<()> { diff --git a/rust/agents/relayer/src/msg/gas_payment/mod.rs b/rust/agents/relayer/src/msg/gas_payment/mod.rs index cd9dd61c0..a07210391 100644 --- a/rust/agents/relayer/src/msg/gas_payment/mod.rs +++ b/rust/agents/relayer/src/msg/gas_payment/mod.rs @@ -19,6 +19,8 @@ use crate::{ mod policies; +pub const GAS_EXPENDITURE_LOG_MESSAGE: &str = "Recording gas expenditure for message"; + #[async_trait] pub trait GasPaymentPolicy: Debug + Send + Sync { /// Returns Some(gas_limit) if the policy has approved the transaction or @@ -132,6 +134,13 @@ impl GasPaymentEnforcer { } pub fn record_tx_outcome(&self, message: &HyperlaneMessage, outcome: TxOutcome) -> Result<()> { + // This log is required in E2E, hence the use of a `const` + debug!( + msg=%message, + ?outcome, + "{}", + GAS_EXPENDITURE_LOG_MESSAGE, + ); self.db.process_gas_expenditure(InterchainGasExpenditure { message_id: message.id(), gas_used: outcome.gas_used, diff --git a/rust/agents/relayer/src/msg/mod.rs b/rust/agents/relayer/src/msg/mod.rs index 60c2ce0c5..dd7bac22b 100644 --- a/rust/agents/relayer/src/msg/mod.rs +++ b/rust/agents/relayer/src/msg/mod.rs @@ -30,5 +30,6 @@ pub(crate) mod metadata; pub(crate) mod op_queue; pub(crate) mod op_submitter; pub(crate) mod pending_message; -pub(crate) mod pending_operation; pub(crate) mod processor; + +pub use gas_payment::GAS_EXPENDITURE_LOG_MESSAGE; diff --git a/rust/agents/relayer/src/msg/op_queue.rs b/rust/agents/relayer/src/msg/op_queue.rs index ef8c2ad2d..007208554 100644 --- a/rust/agents/relayer/src/msg/op_queue.rs +++ b/rust/agents/relayer/src/msg/op_queue.rs @@ -1,24 +1,20 @@ use std::{cmp::Reverse, collections::BinaryHeap, sync::Arc}; use derive_new::new; -use hyperlane_core::MpmcReceiver; +use hyperlane_core::{PendingOperation, QueueOperation}; use prometheus::{IntGauge, IntGaugeVec}; -use tokio::sync::Mutex; -use tracing::{info, instrument}; +use tokio::sync::{broadcast::Receiver, Mutex}; +use tracing::{debug, info, instrument}; use crate::server::MessageRetryRequest; -use super::pending_operation::PendingOperation; - -pub type QueueOperation = Box; - /// Queue of generic operations that can be submitted to a destination chain. /// Includes logic for maintaining queue metrics by the destination and `app_context` of an operation #[derive(Debug, Clone, new)] pub struct OpQueue { metrics: IntGaugeVec, queue_metrics_label: String, - retry_rx: MpmcReceiver, + retry_rx: Arc>>, #[new(default)] queue: Arc>>>, } @@ -41,7 +37,7 @@ impl OpQueue { } /// Pop multiple elements at once from the queue and update metrics - #[instrument(skip(self), ret, fields(queue_label=%self.queue_metrics_label), level = "debug")] + #[instrument(skip(self), fields(queue_label=%self.queue_metrics_label), level = "debug")] pub async fn pop_many(&mut self, limit: usize) -> Vec { self.process_retry_requests().await; let mut queue = self.queue.lock().await; @@ -55,6 +51,15 @@ impl OpQueue { break; } } + // This function is called very often by the op_submitter tasks, so only log when there are operations to pop + // to avoid spamming the logs + if !popped.is_empty() { + debug!( + queue_label = %self.queue_metrics_label, + operations = ?popped, + "Popped OpQueue operations" + ); + } popped } @@ -64,7 +69,7 @@ impl OpQueue { // The other consideration is whether to put the channel receiver in the OpQueue or in a dedicated task // that also holds an Arc to the Mutex. For simplicity, we'll put it in the OpQueue for now. let mut message_retry_requests = vec![]; - while let Ok(message_id) = self.retry_rx.receiver.try_recv() { + while let Ok(message_id) = self.retry_rx.lock().await.try_recv() { message_retry_requests.push(message_id); } if message_retry_requests.is_empty() { @@ -101,15 +106,15 @@ impl OpQueue { #[cfg(test)] mod test { use super::*; - use crate::msg::pending_operation::PendingOperationResult; use hyperlane_core::{ - HyperlaneDomain, HyperlaneMessage, KnownHyperlaneDomain, MpmcChannel, TryBatchAs, - TxOutcome, H256, + HyperlaneDomain, HyperlaneMessage, KnownHyperlaneDomain, PendingOperationResult, + TryBatchAs, TxOutcome, H256, U256, }; use std::{ collections::VecDeque, time::{Duration, Instant}, }; + use tokio::sync; #[derive(Debug, Clone)] struct MockPendingOperation { @@ -174,6 +179,10 @@ mod test { todo!() } + fn get_tx_cost_estimate(&self) -> Option { + todo!() + } + /// This will be called after the operation has been submitted and is /// responsible for checking if the operation has reached a point at /// which we consider it safe from reorgs. @@ -181,6 +190,14 @@ mod test { todo!() } + fn set_operation_outcome( + &mut self, + _submission_outcome: TxOutcome, + _submission_estimated_cost: U256, + ) { + todo!() + } + fn next_attempt_after(&self) -> Option { Some( Instant::now() @@ -212,13 +229,17 @@ mod test { #[tokio::test] async fn test_multiple_op_queues_message_id() { let (metrics, queue_metrics_label) = dummy_metrics_and_label(); - let mpmc_channel = MpmcChannel::new(100); + let broadcaster = sync::broadcast::Sender::new(100); let mut op_queue_1 = OpQueue::new( metrics.clone(), queue_metrics_label.clone(), - mpmc_channel.receiver(), + Arc::new(Mutex::new(broadcaster.subscribe())), + ); + let mut op_queue_2 = OpQueue::new( + metrics, + queue_metrics_label, + Arc::new(Mutex::new(broadcaster.subscribe())), ); - let mut op_queue_2 = OpQueue::new(metrics, queue_metrics_label, mpmc_channel.receiver()); // Add some operations to the queue with increasing `next_attempt_after` values let destination_domain: HyperlaneDomain = KnownHyperlaneDomain::Injective.into(); @@ -244,11 +265,10 @@ mod test { } // Retry by message ids - let mpmc_tx = mpmc_channel.sender(); - mpmc_tx + broadcaster .send(MessageRetryRequest::MessageId(op_ids[1])) .unwrap(); - mpmc_tx + broadcaster .send(MessageRetryRequest::MessageId(op_ids[2])) .unwrap(); @@ -278,11 +298,11 @@ mod test { #[tokio::test] async fn test_destination_domain() { let (metrics, queue_metrics_label) = dummy_metrics_and_label(); - let mpmc_channel = MpmcChannel::new(100); + let broadcaster = sync::broadcast::Sender::new(100); let mut op_queue = OpQueue::new( metrics.clone(), queue_metrics_label.clone(), - mpmc_channel.receiver(), + Arc::new(Mutex::new(broadcaster.subscribe())), ); // Add some operations to the queue with increasing `next_attempt_after` values @@ -304,8 +324,7 @@ mod test { } // Retry by domain - let mpmc_tx = mpmc_channel.sender(); - mpmc_tx + broadcaster .send(MessageRetryRequest::DestinationDomain( destination_domain_2.id(), )) diff --git a/rust/agents/relayer/src/msg/op_submitter.rs b/rust/agents/relayer/src/msg/op_submitter.rs index dc3091149..20eb93616 100644 --- a/rust/agents/relayer/src/msg/op_submitter.rs +++ b/rust/agents/relayer/src/msg/op_submitter.rs @@ -1,10 +1,14 @@ +use std::sync::Arc; use std::time::Duration; use derive_new::new; use futures::future::join_all; use futures_util::future::try_join_all; +use hyperlane_core::total_estimated_cost; use prometheus::{IntCounter, IntGaugeVec}; +use tokio::sync::broadcast::Sender; use tokio::sync::mpsc; +use tokio::sync::Mutex; use tokio::task::JoinHandle; use tokio::time::sleep; use tokio_metrics::TaskMonitor; @@ -14,14 +18,13 @@ use tracing::{info, warn}; use hyperlane_base::CoreMetrics; use hyperlane_core::{ BatchItem, ChainCommunicationError, ChainResult, HyperlaneDomain, HyperlaneDomainProtocol, - HyperlaneMessage, MpmcReceiver, TxOutcome, + HyperlaneMessage, PendingOperationResult, QueueOperation, TxOutcome, }; use crate::msg::pending_message::CONFIRM_DELAY; use crate::server::MessageRetryRequest; -use super::op_queue::{OpQueue, QueueOperation}; -use super::pending_operation::*; +use super::op_queue::OpQueue; /// SerialSubmitter accepts operations over a channel. It is responsible for /// executing the right strategy to deliver those messages to the destination @@ -77,7 +80,7 @@ pub struct SerialSubmitter { /// Receiver for new messages to submit. rx: mpsc::UnboundedReceiver, /// Receiver for retry requests. - retry_rx: MpmcReceiver, + retry_tx: Sender, /// Metrics for serial submitter. metrics: SerialSubmitterMetrics, /// Max batch size for submitting messages @@ -101,24 +104,24 @@ impl SerialSubmitter { domain, metrics, rx: rx_prepare, - retry_rx, + retry_tx, max_batch_size, task_monitor, } = self; let prepare_queue = OpQueue::new( metrics.submitter_queue_length.clone(), "prepare_queue".to_string(), - retry_rx.clone(), + Arc::new(Mutex::new(retry_tx.subscribe())), ); let submit_queue = OpQueue::new( metrics.submitter_queue_length.clone(), "submit_queue".to_string(), - retry_rx.clone(), + Arc::new(Mutex::new(retry_tx.subscribe())), ); let confirm_queue = OpQueue::new( metrics.submitter_queue_length.clone(), "confirm_queue".to_string(), - retry_rx, + Arc::new(Mutex::new(retry_tx.subscribe())), ); let tasks = [ @@ -425,11 +428,10 @@ impl OperationBatch { async fn submit(self, confirm_queue: &mut OpQueue, metrics: &SerialSubmitterMetrics) { match self.try_submit_as_batch(metrics).await { Ok(outcome) => { - // TODO: use the `tx_outcome` with the total gas expenditure - // We'll need to proportionally set `used_gas` based on the tx_outcome, so it can be updated in the confirm step - // which means we need to add a `set_transaction_outcome` fn to `PendingOperation` info!(outcome=?outcome, batch_size=self.operations.len(), batch=?self.operations, "Submitted transaction batch"); + let total_estimated_cost = total_estimated_cost(&self.operations); for mut op in self.operations { + op.set_operation_outcome(outcome.clone(), total_estimated_cost); op.set_next_attempt_after(CONFIRM_DELAY); confirm_queue.push(op).await; } @@ -459,8 +461,6 @@ impl OperationBatch { return Err(ChainCommunicationError::BatchIsEmpty); }; - // We use the estimated gas limit from the prior call to - // `process_estimate_costs` to avoid a second gas estimation. let outcome = first_item.mailbox.process_batch(&batch).await?; metrics.ops_submitted.inc_by(self.operations.len() as u64); Ok(outcome) diff --git a/rust/agents/relayer/src/msg/pending_message.rs b/rust/agents/relayer/src/msg/pending_message.rs index b2f8369d0..a0c373adc 100644 --- a/rust/agents/relayer/src/msg/pending_message.rs +++ b/rust/agents/relayer/src/msg/pending_message.rs @@ -9,8 +9,9 @@ use derive_new::new; use eyre::Result; use hyperlane_base::{db::HyperlaneRocksDB, CoreMetrics}; use hyperlane_core::{ - BatchItem, ChainCommunicationError, ChainResult, HyperlaneChain, HyperlaneDomain, - HyperlaneMessage, Mailbox, MessageSubmissionData, TryBatchAs, TxOutcome, H256, U256, + gas_used_by_operation, make_op_try, BatchItem, ChainCommunicationError, ChainResult, + HyperlaneChain, HyperlaneDomain, HyperlaneMessage, Mailbox, MessageSubmissionData, + PendingOperation, PendingOperationResult, TryBatchAs, TxOutcome, H256, U256, }; use prometheus::{IntCounter, IntGauge}; use tracing::{debug, error, info, instrument, trace, warn}; @@ -18,7 +19,6 @@ use tracing::{debug, error, info, instrument, trace, warn}; use super::{ gas_payment::GasPaymentEnforcer, metadata::{BaseMetadataBuilder, MessageMetadataBuilder, MetadataBuilder}, - pending_operation::*, }; pub const CONFIRM_DELAY: Duration = if cfg!(any(test, feature = "test-utils")) { @@ -259,7 +259,7 @@ impl PendingOperation for PendingMessage { let state = self .submission_data - .take() + .clone() .expect("Pending message must be prepared before it can be submitted"); // We use the estimated gas limit from the prior call to @@ -271,7 +271,7 @@ impl PendingOperation for PendingMessage { .await; match tx_outcome { Ok(outcome) => { - self.set_submission_outcome(outcome); + self.set_operation_outcome(outcome, state.gas_limit); } Err(e) => { error!(error=?e, "Error when processing message"); @@ -283,6 +283,10 @@ impl PendingOperation for PendingMessage { self.submission_outcome = Some(outcome); } + fn get_tx_cost_estimate(&self) -> Option { + self.submission_data.as_ref().map(|d| d.gas_limit) + } + async fn confirm(&mut self) -> PendingOperationResult { make_op_try!(|| { // Provider error; just try again later @@ -313,15 +317,6 @@ impl PendingOperation for PendingMessage { ); PendingOperationResult::Success } else { - if let Some(outcome) = &self.submission_outcome { - if let Err(e) = self - .ctx - .origin_gas_payment_enforcer - .record_tx_outcome(&self.message, outcome.clone()) - { - error!(error=?e, "Error when recording tx outcome"); - } - } warn!( tx_outcome=?self.submission_outcome, message_id=?self.message.id(), @@ -331,6 +326,50 @@ impl PendingOperation for PendingMessage { } } + fn set_operation_outcome( + &mut self, + submission_outcome: TxOutcome, + submission_estimated_cost: U256, + ) { + let Some(operation_estimate) = self.get_tx_cost_estimate() else { + warn!("Cannot set operation outcome without a cost estimate set previously"); + return; + }; + // calculate the gas used by the operation + let gas_used_by_operation = match gas_used_by_operation( + &submission_outcome, + submission_estimated_cost, + operation_estimate, + ) { + Ok(gas_used_by_operation) => gas_used_by_operation, + Err(e) => { + warn!(error = %e, "Error when calculating gas used by operation, falling back to charging the full cost of the tx. Are gas estimates enabled for this chain?"); + submission_outcome.gas_used + } + }; + let operation_outcome = TxOutcome { + gas_used: gas_used_by_operation, + ..submission_outcome + }; + // record it in the db, to subtract from the sender's igp allowance + if let Err(e) = self + .ctx + .origin_gas_payment_enforcer + .record_tx_outcome(&self.message, operation_outcome.clone()) + { + error!(error=?e, "Error when recording tx outcome"); + } + // set the outcome in `Self` as well, for later logging + self.set_submission_outcome(operation_outcome); + debug!( + actual_gas_for_message = ?gas_used_by_operation, + message_gas_estimate = ?operation_estimate, + submission_gas_estimate = ?submission_estimated_cost, + message = ?self.message, + "Gas used by message submission" + ); + } + fn next_attempt_after(&self) -> Option { self.next_attempt_after } @@ -343,7 +382,6 @@ impl PendingOperation for PendingMessage { self.reset_attempts(); } - #[cfg(test)] fn set_retries(&mut self, retries: u32) { self.set_retries(retries); } diff --git a/rust/agents/relayer/src/msg/processor.rs b/rust/agents/relayer/src/msg/processor.rs index 166ee6561..6545d480a 100644 --- a/rust/agents/relayer/src/msg/processor.rs +++ b/rust/agents/relayer/src/msg/processor.rs @@ -13,12 +13,12 @@ use hyperlane_base::{ db::{HyperlaneRocksDB, ProcessMessage}, CoreMetrics, }; -use hyperlane_core::{HyperlaneDomain, HyperlaneMessage}; +use hyperlane_core::{HyperlaneDomain, HyperlaneMessage, QueueOperation}; use prometheus::IntGauge; use tokio::sync::mpsc::UnboundedSender; use tracing::{debug, instrument, trace}; -use super::{metadata::AppContextClassifier, op_queue::QueueOperation, pending_message::*}; +use super::{metadata::AppContextClassifier, pending_message::*}; use crate::{processor::ProcessorExt, settings::matching_list::MatchingList}; /// Finds unprocessed messages from an origin and submits then through a channel diff --git a/rust/agents/relayer/src/relayer.rs b/rust/agents/relayer/src/relayer.rs index 0496e38ca..4206c0584 100644 --- a/rust/agents/relayer/src/relayer.rs +++ b/rust/agents/relayer/src/relayer.rs @@ -13,13 +13,15 @@ use hyperlane_base::{ metrics::{AgentMetrics, MetricsUpdater}, settings::ChainConf, BaseAgent, ChainMetrics, ContractSyncMetrics, ContractSyncer, CoreMetrics, HyperlaneAgentCore, + SyncOptions, }; use hyperlane_core::{ - HyperlaneDomain, HyperlaneMessage, InterchainGasPayment, MerkleTreeInsertion, MpmcChannel, - MpmcReceiver, U256, + HyperlaneDomain, HyperlaneMessage, InterchainGasPayment, MerkleTreeInsertion, QueueOperation, + H512, U256, }; use tokio::{ sync::{ + broadcast::{Receiver, Sender}, mpsc::{self, UnboundedReceiver, UnboundedSender}, RwLock, }, @@ -33,7 +35,6 @@ use crate::{ msg::{ gas_payment::GasPaymentEnforcer, metadata::{BaseMetadataBuilder, IsmAwareAppContextClassifier}, - op_queue::QueueOperation, op_submitter::{SerialSubmitter, SerialSubmitterMetrics}, pending_message::{MessageContext, MessageSubmissionMetrics}, processor::{MessageProcessor, MessageProcessorMetrics}, @@ -134,7 +135,7 @@ impl BaseAgent for Relayer { let contract_sync_metrics = Arc::new(ContractSyncMetrics::new(&core_metrics)); - let message_syncs = settings + let message_syncs: HashMap<_, Arc>> = settings .contract_syncs::( settings.origin_chains.iter(), &core_metrics, @@ -305,8 +306,8 @@ impl BaseAgent for Relayer { } // run server - let mpmc_channel = MpmcChannel::::new(ENDPOINT_MESSAGES_QUEUE_SIZE); - let custom_routes = relayer_server::routes(mpmc_channel.sender()); + let sender = Sender::::new(ENDPOINT_MESSAGES_QUEUE_SIZE); + let custom_routes = relayer_server::routes(sender.clone()); let server = self .core @@ -328,7 +329,7 @@ impl BaseAgent for Relayer { self.run_destination_submitter( dest_domain, receive_channel, - mpmc_channel.receiver(), + sender.clone(), // Default to submitting one message at a time if there is no batch config self.core.settings.chains[dest_domain.name()] .connection @@ -352,14 +353,26 @@ impl BaseAgent for Relayer { } for origin in &self.origin_chains { + let maybe_broadcaster = self + .message_syncs + .get(origin) + .and_then(|sync| sync.get_broadcaster()); tasks.push(self.run_message_sync(origin, task_monitor.clone()).await); tasks.push( - self.run_interchain_gas_payment_sync(origin, task_monitor.clone()) - .await, + self.run_interchain_gas_payment_sync( + origin, + maybe_broadcaster.clone().map(|b| b.subscribe()), + task_monitor.clone(), + ) + .await, ); tasks.push( - self.run_merkle_tree_hook_syncs(origin, task_monitor.clone()) - .await, + self.run_merkle_tree_hook_syncs( + origin, + maybe_broadcaster.map(|b| b.subscribe()), + task_monitor.clone(), + ) + .await, ); } @@ -394,7 +407,7 @@ impl Relayer { tokio::spawn(TaskMonitor::instrument(&task_monitor, async move { contract_sync .clone() - .sync("dispatched_messages", cursor) + .sync("dispatched_messages", cursor.into()) .await })) .instrument(info_span!("MessageSync")) @@ -403,6 +416,7 @@ impl Relayer { async fn run_interchain_gas_payment_sync( &self, origin: &HyperlaneDomain, + tx_id_receiver: Option>, task_monitor: TaskMonitor, ) -> Instrumented> { let index_settings = self.as_ref().settings.chains[origin.name()].index_settings(); @@ -413,7 +427,13 @@ impl Relayer { .clone(); let cursor = contract_sync.cursor(index_settings).await; tokio::spawn(TaskMonitor::instrument(&task_monitor, async move { - contract_sync.clone().sync("gas_payments", cursor).await + contract_sync + .clone() + .sync( + "gas_payments", + SyncOptions::new(Some(cursor), tx_id_receiver), + ) + .await })) .instrument(info_span!("IgpSync")) } @@ -421,13 +441,20 @@ impl Relayer { async fn run_merkle_tree_hook_syncs( &self, origin: &HyperlaneDomain, + tx_id_receiver: Option>, task_monitor: TaskMonitor, ) -> Instrumented> { let index_settings = self.as_ref().settings.chains[origin.name()].index.clone(); let contract_sync = self.merkle_tree_hook_syncs.get(origin).unwrap().clone(); let cursor = contract_sync.cursor(index_settings).await; tokio::spawn(TaskMonitor::instrument(&task_monitor, async move { - contract_sync.clone().sync("merkle_tree_hook", cursor).await + contract_sync + .clone() + .sync( + "merkle_tree_hook", + SyncOptions::new(Some(cursor), tx_id_receiver), + ) + .await })) .instrument(info_span!("MerkleTreeHookSync")) } @@ -498,7 +525,7 @@ impl Relayer { &self, destination: &HyperlaneDomain, receiver: UnboundedReceiver, - retry_receiver_channel: MpmcReceiver, + retry_receiver_channel: Sender, batch_size: u32, task_monitor: TaskMonitor, ) -> Instrumented> { diff --git a/rust/agents/relayer/src/server.rs b/rust/agents/relayer/src/server.rs index 9f6936a22..264ef0380 100644 --- a/rust/agents/relayer/src/server.rs +++ b/rust/agents/relayer/src/server.rs @@ -3,13 +3,11 @@ use axum::{ routing, Router, }; use derive_new::new; -use hyperlane_core::{ChainCommunicationError, H256}; +use hyperlane_core::{ChainCommunicationError, QueueOperation, H256}; use serde::Deserialize; use std::str::FromStr; use tokio::sync::broadcast::Sender; -use crate::msg::op_queue::QueueOperation; - const MESSAGE_RETRY_API_BASE: &str = "/message_retry"; pub const ENDPOINT_MESSAGES_QUEUE_SIZE: usize = 1_000; @@ -109,12 +107,12 @@ mod tests { use super::*; use axum::http::StatusCode; use ethers::utils::hex::ToHex; - use hyperlane_core::{MpmcChannel, MpmcReceiver}; use std::net::SocketAddr; + use tokio::sync::broadcast::{Receiver, Sender}; - fn setup_test_server() -> (SocketAddr, MpmcReceiver) { - let mpmc_channel = MpmcChannel::::new(ENDPOINT_MESSAGES_QUEUE_SIZE); - let message_retry_api = MessageRetryApi::new(mpmc_channel.sender()); + fn setup_test_server() -> (SocketAddr, Receiver) { + let broadcast_tx = Sender::::new(ENDPOINT_MESSAGES_QUEUE_SIZE); + let message_retry_api = MessageRetryApi::new(broadcast_tx.clone()); let (path, retry_router) = message_retry_api.get_route(); let app = Router::new().nest(path, retry_router); @@ -124,7 +122,7 @@ mod tests { let addr = server.local_addr(); tokio::spawn(server); - (addr, mpmc_channel.receiver()) + (addr, broadcast_tx.subscribe()) } #[tokio::test] @@ -148,7 +146,7 @@ mod tests { assert_eq!(response.status(), StatusCode::OK); assert_eq!( - rx.receiver.try_recv().unwrap(), + rx.try_recv().unwrap(), MessageRetryRequest::MessageId(message_id) ); } @@ -172,7 +170,7 @@ mod tests { assert_eq!(response.status(), StatusCode::OK); assert_eq!( - rx.receiver.try_recv().unwrap(), + rx.try_recv().unwrap(), MessageRetryRequest::DestinationDomain(destination_domain) ); } diff --git a/rust/agents/relayer/src/settings/matching_list.rs b/rust/agents/relayer/src/settings/matching_list.rs index da037f19b..097855f17 100644 --- a/rust/agents/relayer/src/settings/matching_list.rs +++ b/rust/agents/relayer/src/settings/matching_list.rs @@ -1,4 +1,4 @@ -//! The correct settings shape is defined in the TypeScript SDK metadata. While the the exact shape +//! The correct settings shape is defined in the TypeScript SDK metadata. While the exact shape //! and validations it defines are not applied here, we should mirror them. //! ANY CHANGES HERE NEED TO BE REFLECTED IN THE TYPESCRIPT SDK. @@ -267,13 +267,13 @@ impl<'a> From<&'a HyperlaneMessage> for MatchInfo<'a> { impl MatchingList { /// Check if a message matches any of the rules. - /// - `default`: What to return if the the matching list is empty. + /// - `default`: What to return if the matching list is empty. pub fn msg_matches(&self, msg: &HyperlaneMessage, default: bool) -> bool { self.matches(msg.into(), default) } /// Check if a message matches any of the rules. - /// - `default`: What to return if the the matching list is empty. + /// - `default`: What to return if the matching list is empty. fn matches(&self, info: MatchInfo, default: bool) -> bool { if let Some(rules) = &self.0 { matches_any_rule(rules.iter(), info) diff --git a/rust/agents/relayer/src/settings/mod.rs b/rust/agents/relayer/src/settings/mod.rs index c6934a77c..2e6b00573 100644 --- a/rust/agents/relayer/src/settings/mod.rs +++ b/rust/agents/relayer/src/settings/mod.rs @@ -1,6 +1,6 @@ //! Relayer configuration //! -//! The correct settings shape is defined in the TypeScript SDK metadata. While the the exact shape +//! The correct settings shape is defined in the TypeScript SDK metadata. While the exact shape //! and validations it defines are not applied here, we should mirror them. //! ANY CHANGES HERE NEED TO BE REFLECTED IN THE TYPESCRIPT SDK. diff --git a/rust/agents/scraper/src/agent.rs b/rust/agents/scraper/src/agent.rs index d71343281..f33f00556 100644 --- a/rust/agents/scraper/src/agent.rs +++ b/rust/agents/scraper/src/agent.rs @@ -5,10 +5,13 @@ use derive_more::AsRef; use futures::future::try_join_all; use hyperlane_base::{ metrics::AgentMetrics, settings::IndexSettings, BaseAgent, ChainMetrics, ContractSyncMetrics, - ContractSyncer, CoreMetrics, HyperlaneAgentCore, MetricsUpdater, + ContractSyncer, CoreMetrics, HyperlaneAgentCore, MetricsUpdater, SyncOptions, +}; +use hyperlane_core::{Delivery, HyperlaneDomain, HyperlaneMessage, InterchainGasPayment, H512}; +use tokio::{ + sync::broadcast::{Receiver, Sender}, + task::JoinHandle, }; -use hyperlane_core::{Delivery, HyperlaneDomain, HyperlaneMessage, InterchainGasPayment}; -use tokio::task::JoinHandle; use tracing::{info_span, instrument::Instrumented, trace, Instrument}; use crate::{chain_scraper::HyperlaneSqlDb, db::ScraperDb, settings::ScraperSettings}; @@ -135,16 +138,16 @@ impl Scraper { let domain = scraper.domain.clone(); let mut tasks = Vec::with_capacity(2); - tasks.push( - self.build_message_indexer( + let (message_indexer, maybe_broadcaster) = self + .build_message_indexer( domain.clone(), self.core_metrics.clone(), self.contract_sync_metrics.clone(), db.clone(), index_settings.clone(), ) - .await, - ); + .await; + tasks.push(message_indexer); tasks.push( self.build_delivery_indexer( domain.clone(), @@ -152,6 +155,7 @@ impl Scraper { self.contract_sync_metrics.clone(), db.clone(), index_settings.clone(), + maybe_broadcaster.clone().map(|b| b.subscribe()), ) .await, ); @@ -162,6 +166,7 @@ impl Scraper { self.contract_sync_metrics.clone(), db, index_settings.clone(), + maybe_broadcaster.map(|b| b.subscribe()), ) .await, ); @@ -182,7 +187,7 @@ impl Scraper { contract_sync_metrics: Arc, db: HyperlaneSqlDb, index_settings: IndexSettings, - ) -> Instrumented> { + ) -> (Instrumented>, Option>) { let sync = self .as_ref() .settings @@ -195,9 +200,12 @@ impl Scraper { .await .unwrap(); let cursor = sync.cursor(index_settings.clone()).await; - tokio::spawn(async move { sync.sync("message_dispatch", cursor).await }).instrument( - info_span!("ChainContractSync", chain=%domain.name(), event="message_dispatch"), - ) + let maybe_broadcaser = sync.get_broadcaster(); + let task = tokio::spawn(async move { sync.sync("message_dispatch", cursor.into()).await }) + .instrument( + info_span!("ChainContractSync", chain=%domain.name(), event="message_dispatch"), + ); + (task, maybe_broadcaser) } async fn build_delivery_indexer( @@ -207,6 +215,7 @@ impl Scraper { contract_sync_metrics: Arc, db: HyperlaneSqlDb, index_settings: IndexSettings, + tx_id_receiver: Option>, ) -> Instrumented> { let sync = self .as_ref() @@ -222,8 +231,11 @@ impl Scraper { let label = "message_delivery"; let cursor = sync.cursor(index_settings.clone()).await; - tokio::spawn(async move { sync.sync(label, cursor).await }) - .instrument(info_span!("ChainContractSync", chain=%domain.name(), event=label)) + tokio::spawn(async move { + sync.sync(label, SyncOptions::new(Some(cursor), tx_id_receiver)) + .await + }) + .instrument(info_span!("ChainContractSync", chain=%domain.name(), event=label)) } async fn build_interchain_gas_payment_indexer( @@ -233,6 +245,7 @@ impl Scraper { contract_sync_metrics: Arc, db: HyperlaneSqlDb, index_settings: IndexSettings, + tx_id_receiver: Option>, ) -> Instrumented> { let sync = self .as_ref() @@ -248,7 +261,10 @@ impl Scraper { let label = "gas_payment"; let cursor = sync.cursor(index_settings.clone()).await; - tokio::spawn(async move { sync.sync(label, cursor).await }) - .instrument(info_span!("ChainContractSync", chain=%domain.name(), event=label)) + tokio::spawn(async move { + sync.sync(label, SyncOptions::new(Some(cursor), tx_id_receiver)) + .await + }) + .instrument(info_span!("ChainContractSync", chain=%domain.name(), event=label)) } } diff --git a/rust/agents/scraper/src/settings.rs b/rust/agents/scraper/src/settings.rs index b4bdfbb4d..b6c9eb7e4 100644 --- a/rust/agents/scraper/src/settings.rs +++ b/rust/agents/scraper/src/settings.rs @@ -1,6 +1,6 @@ //! Scraper configuration. //! -//! The correct settings shape is defined in the TypeScript SDK metadata. While the the exact shape +//! The correct settings shape is defined in the TypeScript SDK metadata. While the exact shape //! and validations it defines are not applied here, we should mirror them. //! ANY CHANGES HERE NEED TO BE REFLECTED IN THE TYPESCRIPT SDK. diff --git a/rust/agents/validator/src/settings.rs b/rust/agents/validator/src/settings.rs index f6870a31c..70158d267 100644 --- a/rust/agents/validator/src/settings.rs +++ b/rust/agents/validator/src/settings.rs @@ -1,6 +1,6 @@ //! Validator configuration. //! -//! The correct settings shape is defined in the TypeScript SDK metadata. While the the exact shape +//! The correct settings shape is defined in the TypeScript SDK metadata. While the exact shape //! and validations it defines are not applied here, we should mirror them. //! ANY CHANGES HERE NEED TO BE REFLECTED IN THE TYPESCRIPT SDK. diff --git a/rust/agents/validator/src/validator.rs b/rust/agents/validator/src/validator.rs index 043ac9249..23e96aeb5 100644 --- a/rust/agents/validator/src/validator.rs +++ b/rust/agents/validator/src/validator.rs @@ -210,7 +210,10 @@ impl Validator { let contract_sync = self.merkle_tree_hook_sync.clone(); let cursor = contract_sync.cursor(index_settings).await; tokio::spawn(async move { - contract_sync.clone().sync("merkle_tree_hook", cursor).await; + contract_sync + .clone() + .sync("merkle_tree_hook", cursor.into()) + .await; }) .instrument(info_span!("MerkleTreeHookSyncer")) } diff --git a/rust/chains/hyperlane-cosmos/src/interchain_gas.rs b/rust/chains/hyperlane-cosmos/src/interchain_gas.rs index 4ba2ca87a..4444a56ea 100644 --- a/rust/chains/hyperlane-cosmos/src/interchain_gas.rs +++ b/rust/chains/hyperlane-cosmos/src/interchain_gas.rs @@ -202,7 +202,7 @@ impl CosmosInterchainGasPaymasterIndexer { #[async_trait] impl Indexer for CosmosInterchainGasPaymasterIndexer { - async fn fetch_logs( + async fn fetch_logs_in_range( &self, range: RangeInclusive, ) -> ChainResult, LogMeta)>> { diff --git a/rust/chains/hyperlane-cosmos/src/mailbox.rs b/rust/chains/hyperlane-cosmos/src/mailbox.rs index 7f686cb85..833b92b89 100644 --- a/rust/chains/hyperlane-cosmos/src/mailbox.rs +++ b/rust/chains/hyperlane-cosmos/src/mailbox.rs @@ -350,7 +350,7 @@ impl CosmosMailboxIndexer { #[async_trait] impl Indexer for CosmosMailboxIndexer { - async fn fetch_logs( + async fn fetch_logs_in_range( &self, range: RangeInclusive, ) -> ChainResult, LogMeta)>> { @@ -397,7 +397,7 @@ impl Indexer for CosmosMailboxIndexer { #[async_trait] impl Indexer for CosmosMailboxIndexer { - async fn fetch_logs( + async fn fetch_logs_in_range( &self, range: RangeInclusive, ) -> ChainResult, LogMeta)>> { diff --git a/rust/chains/hyperlane-cosmos/src/merkle_tree_hook.rs b/rust/chains/hyperlane-cosmos/src/merkle_tree_hook.rs index c8e798096..54acdf80f 100644 --- a/rust/chains/hyperlane-cosmos/src/merkle_tree_hook.rs +++ b/rust/chains/hyperlane-cosmos/src/merkle_tree_hook.rs @@ -283,7 +283,7 @@ impl CosmosMerkleTreeHookIndexer { #[async_trait] impl Indexer for CosmosMerkleTreeHookIndexer { /// Fetch list of logs between `range` of blocks - async fn fetch_logs( + async fn fetch_logs_in_range( &self, range: RangeInclusive, ) -> ChainResult, LogMeta)>> { diff --git a/rust/chains/hyperlane-ethereum/src/contracts/interchain_gas.rs b/rust/chains/hyperlane-ethereum/src/contracts/interchain_gas.rs index 8ed514c83..76345ec8f 100644 --- a/rust/chains/hyperlane-ethereum/src/contracts/interchain_gas.rs +++ b/rust/chains/hyperlane-ethereum/src/contracts/interchain_gas.rs @@ -10,12 +10,14 @@ use ethers::prelude::Middleware; use hyperlane_core::{ ChainCommunicationError, ChainResult, ContractLocator, HyperlaneAbi, HyperlaneChain, HyperlaneContract, HyperlaneDomain, HyperlaneProvider, Indexed, Indexer, - InterchainGasPaymaster, InterchainGasPayment, LogMeta, SequenceAwareIndexer, H160, H256, + InterchainGasPaymaster, InterchainGasPayment, LogMeta, SequenceAwareIndexer, H160, H256, H512, }; use tracing::instrument; +use super::utils::fetch_raw_logs_and_log_meta; use crate::interfaces::i_interchain_gas_paymaster::{ - IInterchainGasPaymaster as EthereumInterchainGasPaymasterInternal, IINTERCHAINGASPAYMASTER_ABI, + GasPaymentFilter, IInterchainGasPaymaster as EthereumInterchainGasPaymasterInternal, + IINTERCHAINGASPAYMASTER_ABI, }; use crate::{BuildableWithProvider, ConnectionConf, EthereumProvider}; @@ -86,7 +88,7 @@ where { /// Note: This call may return duplicates depending on the provider used #[instrument(err, skip(self))] - async fn fetch_logs( + async fn fetch_logs_in_range( &self, range: RangeInclusive, ) -> ChainResult, LogMeta)>> { @@ -124,6 +126,32 @@ where .as_u32() .saturating_sub(self.reorg_period)) } + + async fn fetch_logs_by_tx_hash( + &self, + tx_hash: H512, + ) -> ChainResult, LogMeta)>> { + let logs = fetch_raw_logs_and_log_meta::( + tx_hash, + self.provider.clone(), + self.contract.address(), + ) + .await? + .into_iter() + .map(|(log, log_meta)| { + ( + Indexed::new(InterchainGasPayment { + message_id: H256::from(log.message_id), + destination: log.destination_domain, + payment: log.payment.into(), + gas_amount: log.gas_amount.into(), + }), + log_meta, + ) + }) + .collect(); + Ok(logs) + } } #[async_trait] diff --git a/rust/chains/hyperlane-ethereum/src/contracts/mailbox.rs b/rust/chains/hyperlane-ethereum/src/contracts/mailbox.rs index d70c2bfc7..37933f5f4 100644 --- a/rust/chains/hyperlane-ethereum/src/contracts/mailbox.rs +++ b/rust/chains/hyperlane-ethereum/src/contracts/mailbox.rs @@ -11,6 +11,7 @@ use ethers::abi::{AbiEncode, Detokenize}; use ethers::prelude::Middleware; use ethers_contract::builders::ContractCall; use futures_util::future::join_all; +use hyperlane_core::H512; use tracing::instrument; use hyperlane_core::{ @@ -25,10 +26,12 @@ use crate::interfaces::arbitrum_node_interface::ArbitrumNodeInterface; use crate::interfaces::i_mailbox::{ IMailbox as EthereumMailboxInternal, ProcessCall, IMAILBOX_ABI, }; +use crate::interfaces::mailbox::DispatchFilter; use crate::tx::{call_with_lag, fill_tx_gas_params, report_tx}; use crate::{BuildableWithProvider, ConnectionConf, EthereumProvider, TransactionOverrides}; use super::multicall::{self, build_multicall}; +use super::utils::fetch_raw_logs_and_log_meta; impl std::fmt::Display for EthereumMailboxInternal where @@ -134,7 +137,7 @@ where /// Note: This call may return duplicates depending on the provider used #[instrument(err, skip(self))] - async fn fetch_logs( + async fn fetch_logs_in_range( &self, range: RangeInclusive, ) -> ChainResult, LogMeta)>> { @@ -157,6 +160,27 @@ where events.sort_by(|a, b| a.0.inner().nonce.cmp(&b.0.inner().nonce)); Ok(events) } + + async fn fetch_logs_by_tx_hash( + &self, + tx_hash: H512, + ) -> ChainResult, LogMeta)>> { + let logs = fetch_raw_logs_and_log_meta::( + tx_hash, + self.provider.clone(), + self.contract.address(), + ) + .await? + .into_iter() + .map(|(log, log_meta)| { + ( + HyperlaneMessage::from(log.message.to_vec()).into(), + log_meta, + ) + }) + .collect(); + Ok(logs) + } } #[async_trait] @@ -183,7 +207,7 @@ where /// Note: This call may return duplicates depending on the provider used #[instrument(err, skip(self))] - async fn fetch_logs( + async fn fetch_logs_in_range( &self, range: RangeInclusive, ) -> ChainResult, LogMeta)>> { diff --git a/rust/chains/hyperlane-ethereum/src/contracts/merkle_tree_hook.rs b/rust/chains/hyperlane-ethereum/src/contracts/merkle_tree_hook.rs index a94ceff32..5836838ef 100644 --- a/rust/chains/hyperlane-ethereum/src/contracts/merkle_tree_hook.rs +++ b/rust/chains/hyperlane-ethereum/src/contracts/merkle_tree_hook.rs @@ -11,13 +11,17 @@ use tracing::instrument; use hyperlane_core::{ ChainCommunicationError, ChainResult, Checkpoint, ContractLocator, HyperlaneChain, HyperlaneContract, HyperlaneDomain, HyperlaneProvider, Indexed, Indexer, LogMeta, - MerkleTreeHook, MerkleTreeInsertion, SequenceAwareIndexer, H256, + MerkleTreeHook, MerkleTreeInsertion, SequenceAwareIndexer, H256, H512, }; -use crate::interfaces::merkle_tree_hook::{MerkleTreeHook as MerkleTreeHookContract, Tree}; +use crate::interfaces::merkle_tree_hook::{ + InsertedIntoTreeFilter, MerkleTreeHook as MerkleTreeHookContract, Tree, +}; use crate::tx::call_with_lag; use crate::{BuildableWithProvider, ConnectionConf, EthereumProvider}; +use super::utils::fetch_raw_logs_and_log_meta; + // We don't need the reverse of this impl, so it's ok to disable the clippy lint #[allow(clippy::from_over_into)] impl Into for Tree { @@ -108,7 +112,7 @@ where { /// Note: This call may return duplicates depending on the provider used #[instrument(err, skip(self))] - async fn fetch_logs( + async fn fetch_logs_in_range( &self, range: RangeInclusive, ) -> ChainResult, LogMeta)>> { @@ -142,6 +146,27 @@ where .as_u32() .saturating_sub(self.reorg_period)) } + + async fn fetch_logs_by_tx_hash( + &self, + tx_hash: H512, + ) -> ChainResult, LogMeta)>> { + let logs = fetch_raw_logs_and_log_meta::( + tx_hash, + self.provider.clone(), + self.contract.address(), + ) + .await? + .into_iter() + .map(|(log, log_meta)| { + ( + MerkleTreeInsertion::new(log.index, H256::from(log.message_id)).into(), + log_meta, + ) + }) + .collect(); + Ok(logs) + } } #[async_trait] diff --git a/rust/chains/hyperlane-ethereum/src/contracts/mod.rs b/rust/chains/hyperlane-ethereum/src/contracts/mod.rs index 32ad5b953..1a39fae07 100644 --- a/rust/chains/hyperlane-ethereum/src/contracts/mod.rs +++ b/rust/chains/hyperlane-ethereum/src/contracts/mod.rs @@ -1,11 +1,8 @@ pub use {interchain_gas::*, mailbox::*, merkle_tree_hook::*, validator_announce::*}; mod interchain_gas; - mod mailbox; - mod merkle_tree_hook; - mod multicall; - +mod utils; mod validator_announce; diff --git a/rust/chains/hyperlane-ethereum/src/contracts/utils.rs b/rust/chains/hyperlane-ethereum/src/contracts/utils.rs new file mode 100644 index 000000000..bdf3e52f9 --- /dev/null +++ b/rust/chains/hyperlane-ethereum/src/contracts/utils.rs @@ -0,0 +1,48 @@ +use std::sync::Arc; + +use ethers::{ + abi::RawLog, + providers::Middleware, + types::{H160 as EthersH160, H256 as EthersH256}, +}; +use ethers_contract::{ContractError, EthEvent, LogMeta as EthersLogMeta}; +use hyperlane_core::{ChainResult, LogMeta, H512}; +use tracing::warn; + +pub async fn fetch_raw_logs_and_log_meta( + tx_hash: H512, + provider: Arc, + contract_address: EthersH160, +) -> ChainResult> +where + M: Middleware + 'static, +{ + let ethers_tx_hash: EthersH256 = tx_hash.into(); + let receipt = provider + .get_transaction_receipt(ethers_tx_hash) + .await + .map_err(|err| ContractError::::MiddlewareError(err))?; + let Some(receipt) = receipt else { + warn!(%tx_hash, "No receipt found for tx hash"); + return Ok(vec![]); + }; + + let logs: Vec<(T, LogMeta)> = receipt + .logs + .into_iter() + .filter_map(|log| { + // Filter out logs that aren't emitted by this contract + if log.address != contract_address { + return None; + } + let raw_log = RawLog { + topics: log.topics.clone(), + data: log.data.to_vec(), + }; + let log_meta: EthersLogMeta = (&log).into(); + let event_filter = T::decode_log(&raw_log).ok(); + event_filter.map(|log| (log, log_meta.into())) + }) + .collect(); + Ok(logs) +} diff --git a/rust/chains/hyperlane-fuel/src/interchain_gas.rs b/rust/chains/hyperlane-fuel/src/interchain_gas.rs index d969210a6..3385872c3 100644 --- a/rust/chains/hyperlane-fuel/src/interchain_gas.rs +++ b/rust/chains/hyperlane-fuel/src/interchain_gas.rs @@ -35,7 +35,7 @@ pub struct FuelInterchainGasPaymasterIndexer {} #[async_trait] impl Indexer for FuelInterchainGasPaymasterIndexer { - async fn fetch_logs( + async fn fetch_logs_in_range( &self, range: RangeInclusive, ) -> ChainResult, LogMeta)>> { diff --git a/rust/chains/hyperlane-fuel/src/mailbox.rs b/rust/chains/hyperlane-fuel/src/mailbox.rs index 035fe6e6d..5e8f0cf05 100644 --- a/rust/chains/hyperlane-fuel/src/mailbox.rs +++ b/rust/chains/hyperlane-fuel/src/mailbox.rs @@ -126,7 +126,7 @@ pub struct FuelMailboxIndexer {} #[async_trait] impl Indexer for FuelMailboxIndexer { - async fn fetch_logs( + async fn fetch_logs_in_range( &self, range: RangeInclusive, ) -> ChainResult, LogMeta)>> { @@ -140,7 +140,7 @@ impl Indexer for FuelMailboxIndexer { #[async_trait] impl Indexer for FuelMailboxIndexer { - async fn fetch_logs( + async fn fetch_logs_in_range( &self, range: RangeInclusive, ) -> ChainResult, LogMeta)>> { diff --git a/rust/chains/hyperlane-sealevel/src/interchain_gas.rs b/rust/chains/hyperlane-sealevel/src/interchain_gas.rs index 494583381..beebcb9db 100644 --- a/rust/chains/hyperlane-sealevel/src/interchain_gas.rs +++ b/rust/chains/hyperlane-sealevel/src/interchain_gas.rs @@ -246,7 +246,7 @@ impl SealevelInterchainGasPaymasterIndexer { #[async_trait] impl Indexer for SealevelInterchainGasPaymasterIndexer { #[instrument(err, skip(self))] - async fn fetch_logs( + async fn fetch_logs_in_range( &self, range: RangeInclusive, ) -> ChainResult, LogMeta)>> { diff --git a/rust/chains/hyperlane-sealevel/src/mailbox.rs b/rust/chains/hyperlane-sealevel/src/mailbox.rs index 3fc8393d1..beb4e86c3 100644 --- a/rust/chains/hyperlane-sealevel/src/mailbox.rs +++ b/rust/chains/hyperlane-sealevel/src/mailbox.rs @@ -646,7 +646,7 @@ impl SequenceAwareIndexer for SealevelMailboxIndexer { #[async_trait] impl Indexer for SealevelMailboxIndexer { - async fn fetch_logs( + async fn fetch_logs_in_range( &self, range: RangeInclusive, ) -> ChainResult, LogMeta)>> { @@ -670,7 +670,7 @@ impl Indexer for SealevelMailboxIndexer { #[async_trait] impl Indexer for SealevelMailboxIndexer { - async fn fetch_logs( + async fn fetch_logs_in_range( &self, _range: RangeInclusive, ) -> ChainResult, LogMeta)>> { diff --git a/rust/chains/hyperlane-sealevel/src/merkle_tree_hook.rs b/rust/chains/hyperlane-sealevel/src/merkle_tree_hook.rs index 9fe48053c..8c1132add 100644 --- a/rust/chains/hyperlane-sealevel/src/merkle_tree_hook.rs +++ b/rust/chains/hyperlane-sealevel/src/merkle_tree_hook.rs @@ -83,11 +83,11 @@ pub struct SealevelMerkleTreeHookIndexer(SealevelMailboxIndexer); #[async_trait] impl Indexer for SealevelMerkleTreeHookIndexer { - async fn fetch_logs( + async fn fetch_logs_in_range( &self, range: RangeInclusive, ) -> ChainResult, LogMeta)>> { - let messages = Indexer::::fetch_logs(&self.0, range).await?; + let messages = Indexer::::fetch_logs_in_range(&self.0, range).await?; let merkle_tree_insertions = messages .into_iter() .map(|(m, meta)| (message_to_merkle_tree_insertion(m.inner()).into(), meta)) diff --git a/rust/config/mainnet_config.json b/rust/config/mainnet_config.json index 25bfbb895..c4c494f46 100644 --- a/rust/config/mainnet_config.json +++ b/rust/config/mainnet_config.json @@ -634,13 +634,19 @@ }, "injective": { "bech32Prefix": "inj", + "blockExplorers": [ + ], "blocks": { + "confirmations": 1, + "estimateBlockTime": 1, "reorgPeriod": 10 }, "canonicalAsset": "inj", "chainId": "injective-1", "contractAddressBytes": 20, - "domainId": "6909546", + "displayName": "Injective", + "domainId": 6909546, + "gasCurrencyCoinGeckoId": "injective-protocol", "gasPrice": { "amount": "700000000", "denom": "inj" @@ -658,12 +664,24 @@ "mailbox": "0x0f7fb53961d70687e352aa55cb329ca76edc0c19", "merkleTreeHook": "0x568ad3638447f07def384969f4ea39fae3802962", "name": "injective", + "nativeToken": { + "decimals": 18, + "denom": "inj", + "name": "Injective", + "symbol": "INJ" + }, "protocol": "cosmos", + "restUrls": [ + { + "http": "https://sentry.lcd.injective.network:443" + } + ], "rpcUrls": [ { - "http": "https://injective-rpc.polkachu.com" + "http": "https://sentry.tm.injective.network:443" } ], + "slip44": 118, "validatorAnnounce": "0x1fb225b2fcfbe75e614a1d627de97ff372242eed" }, "mantapacific": { @@ -837,13 +855,25 @@ }, "neutron": { "bech32Prefix": "neutron", + "blockExplorers": [ + { + "apiUrl": "https://www.mintscan.io/neutron", + "family": "other", + "name": "Mintscan", + "url": "https://www.mintscan.io/neutron" + } + ], "blocks": { + "confirmations": 1, + "estimateBlockTime": 3, "reorgPeriod": 1 }, "canonicalAsset": "untrn", "chainId": "neutron-1", "contractAddressBytes": 32, - "domainId": "1853125230", + "displayName": "Neutron", + "domainId": 1853125230, + "gasCurrencyCoinGeckoId": "neutron-3", "gasPrice": { "amount": "0.0053", "denom": "untrn" @@ -858,10 +888,22 @@ "from": 4000000 }, "interchainGasPaymaster": "0x504ee9ac43ec5814e00c7d21869a90ec52becb489636bdf893b7df9d606b5d67", + "isTestnet": false, "mailbox": "0x848426d50eb2104d5c6381ec63757930b1c14659c40db8b8081e516e7c5238fc", "merkleTreeHook": "0xcd30a0001cc1f436c41ef764a712ebabc5a144140e3fd03eafe64a9a24e4e27c", "name": "neutron", + "nativeToken": { + "decimals": 6, + "denom": "untrn", + "name": "Neutron", + "symbol": "NTRN" + }, "protocol": "cosmos", + "restUrls": [ + { + "http": "https://rest-lb.neutron.org" + } + ], "rpcUrls": [ { "http": "https://rpc-kralum.neutron-1.neutron.org" @@ -872,6 +914,7 @@ "prefix": "neutron", "type": "cosmosKey" }, + "slip44": 118, "validatorAnnounce": "0xf3aa0d652226e21ae35cd9035c492ae41725edc9036edf0d6a48701b153b90a0" }, "optimism": { @@ -998,8 +1041,8 @@ "name": "polygon", "nativeToken": { "decimals": 18, - "name": "Ether", - "symbol": "ETH" + "name": "Matic", + "symbol": "MATIC" }, "pausableHook": "0x748040afB89B8FdBb992799808215419d36A0930", "pausableIsm": "0x6741e91fFDC31c7786E3684427c628dad06299B0", diff --git a/rust/config/testnet_config.json b/rust/config/testnet_config.json index 31a60fecf..3680b07c9 100644 --- a/rust/config/testnet_config.json +++ b/rust/config/testnet_config.json @@ -207,6 +207,56 @@ "timelockController": "0x0000000000000000000000000000000000000000", "validatorAnnounce": "0x4f7179A691F8a684f56cF7Fed65171877d30739a" }, + "holesky": { + "blockExplorers": [ + { + "apiUrl": "https://api-holesky.etherscan.io/api", + "family": "etherscan", + "name": "Etherscan", + "url": "https://holesky.etherscan.io" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 13, + "reorgPeriod": 2 + }, + "chainId": 17000, + "displayName": "Holesky", + "domainId": 17000, + "domainRoutingIsmFactory": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD", + "fallbackRoutingHook": "0x07009DA2249c388aD0f416a235AfE90D784e1aAc", + "index": { + "from": 1543015 + }, + "interchainGasPaymaster": "0x5CBf4e70448Ed46c2616b04e9ebc72D29FF0cfA9", + "interchainSecurityModule": "0x751f2b684EeBb916dB777767CCb8fd793C8b2956", + "isTestnet": true, + "mailbox": "0x46f7C5D896bbeC89bE1B19e4485e59b4Be49e9Cc", + "merkleTreeHook": "0x98AAE089CaD930C64a76dD2247a2aC5773a4B8cE", + "name": "holesky", + "nativeToken": { + "decimals": 18, + "name": "Ether", + "symbol": "ETH" + }, + "pausableHook": "0xF7561c34f17A32D5620583A3397C304e7038a7F6", + "protocol": "ethereum", + "protocolFee": "0x6b1bb4ce664Bb4164AEB4d3D2E7DE7450DD8084C", + "proxyAdmin": "0x33dB966328Ea213b0f76eF96CA368AB37779F065", + "rpcUrls": [ + { + "http": "https://ethereum-holesky-rpc.publicnode.com" + } + ], + "staticAggregationHookFactory": "0x589C201a07c26b4725A4A829d772f24423da480B", + "staticAggregationIsmFactory": "0x54148470292C24345fb828B003461a9444414517", + "staticMerkleRootMultisigIsmFactory": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", + "staticMessageIdMultisigIsmFactory": "0x6966b0E55883d49BFB24539356a2f8A673E02039", + "storageGasOracle": "0x2b2a158B4059C840c7aC67399B153bb567D06303", + "testRecipient": "0x86fb9F1c124fB20ff130C41a79a432F770f67AFD", + "validatorAnnounce": "0xAb9B273366D794B7F80B4378bc8Aaca75C6178E2" + }, "plumetestnet": { "aggregationHook": "0x31dF0EEE7Dc7565665468698a0da221225619a1B", "blockExplorers": [ diff --git a/rust/hyperlane-base/src/contract_sync/cursors/mod.rs b/rust/hyperlane-base/src/contract_sync/cursors/mod.rs index c7d7274d6..016454d04 100644 --- a/rust/hyperlane-base/src/contract_sync/cursors/mod.rs +++ b/rust/hyperlane-base/src/contract_sync/cursors/mod.rs @@ -13,8 +13,18 @@ pub enum CursorType { RateLimited, } +// H256 * 1M = 32MB per origin chain worst case +// With one such channel per origin chain. +const TX_ID_CHANNEL_CAPACITY: Option = Some(1_000_000); + pub trait Indexable { + /// Returns the configured cursor type of this type for the given domain, (e.g. `SequenceAware` or `RateLimited`) fn indexing_cursor(domain: HyperlaneDomainProtocol) -> CursorType; + /// Indexing tasks may have channels open between them to share information that improves reliability (such as the txid where a message event was indexed). + /// By default this method is None, and it should return a channel capacity if this indexing task is to broadcast anything to other tasks. + fn broadcast_channel_size() -> Option { + None + } } impl Indexable for HyperlaneMessage { @@ -26,6 +36,11 @@ impl Indexable for HyperlaneMessage { HyperlaneDomainProtocol::Cosmos => CursorType::SequenceAware, } } + + // Only broadcast txids from the message indexing task + fn broadcast_channel_size() -> Option { + TX_ID_CHANNEL_CAPACITY + } } impl Indexable for InterchainGasPayment { diff --git a/rust/hyperlane-base/src/contract_sync/cursors/rate_limited.rs b/rust/hyperlane-base/src/contract_sync/cursors/rate_limited.rs index d85b3618f..242028acb 100644 --- a/rust/hyperlane-base/src/contract_sync/cursors/rate_limited.rs +++ b/rust/hyperlane-base/src/contract_sync/cursors/rate_limited.rs @@ -216,6 +216,16 @@ where } } +impl Debug for RateLimitedContractSyncCursor { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("RateLimitedContractSyncCursor") + .field("tip", &self.tip) + .field("last_tip_update", &self.last_tip_update) + .field("sync_state", &self.sync_state) + .finish() + } +} + #[cfg(test)] pub(crate) mod test { use super::*; @@ -234,7 +244,7 @@ pub(crate) mod test { #[async_trait] impl Indexer<()> for Indexer { - async fn fetch_logs(&self, range: RangeInclusive) -> ChainResult , LogMeta)>>; + async fn fetch_logs_in_range(&self, range: RangeInclusive) -> ChainResult , LogMeta)>>; async fn get_finalized_block_number(&self) -> ChainResult; } } diff --git a/rust/hyperlane-base/src/contract_sync/cursors/sequence_aware/backward.rs b/rust/hyperlane-base/src/contract_sync/cursors/sequence_aware/backward.rs index 3efd04a8d..6a0f66a78 100644 --- a/rust/hyperlane-base/src/contract_sync/cursors/sequence_aware/backward.rs +++ b/rust/hyperlane-base/src/contract_sync/cursors/sequence_aware/backward.rs @@ -9,10 +9,13 @@ use hyperlane_core::{ HyperlaneSequenceAwareIndexerStoreReader, IndexMode, Indexed, LogMeta, SequenceIndexed, }; use itertools::Itertools; +use tokio::time::sleep; use tracing::{debug, instrument, warn}; use super::{LastIndexedSnapshot, TargetSnapshot}; +const MAX_BACKWARD_SYNC_BLOCKING_TIME: Duration = Duration::from_secs(5); + /// A sequence-aware cursor that syncs backward until there are no earlier logs to index. pub(crate) struct BackwardSequenceAwareSyncCursor { /// The max chunk size to query for logs. @@ -32,6 +35,17 @@ pub(crate) struct BackwardSequenceAwareSyncCursor { index_mode: IndexMode, } +impl Debug for BackwardSequenceAwareSyncCursor { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BackwardSequenceAwareSyncCursor") + .field("chunk_size", &self.chunk_size) + .field("last_indexed_snapshot", &self.last_indexed_snapshot) + .field("current_indexing_snapshot", &self.current_indexing_snapshot) + .field("index_mode", &self.index_mode) + .finish() + } +} + impl BackwardSequenceAwareSyncCursor { #[instrument( skip(db), @@ -68,7 +82,11 @@ impl BackwardSequenceAwareSyncCursor { #[instrument(ret)] pub async fn get_next_range(&mut self) -> Result>> { // Skip any already indexed logs. - self.skip_indexed().await?; + tokio::select! { + res = self.skip_indexed() => res?, + // return early to allow the forward cursor to also make progress + _ = sleep(MAX_BACKWARD_SYNC_BLOCKING_TIME) => { return Ok(None); } + }; // If `self.current_indexing_snapshot` is None, we are synced and there are no more ranges to query. // Otherwise, we query the next range, searching for logs prior to and including the current indexing snapshot. @@ -309,17 +327,6 @@ impl BackwardSequenceAwareSyncCursor { } } -impl Debug for BackwardSequenceAwareSyncCursor { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("BackwardSequenceAwareSyncCursor") - .field("chunk_size", &self.chunk_size) - .field("current_indexing_snapshot", &self.current_indexing_snapshot) - .field("last_indexed_snapshot", &self.last_indexed_snapshot) - .field("index_mode", &self.index_mode) - .finish() - } -} - #[async_trait] impl ContractSyncCursor for BackwardSequenceAwareSyncCursor diff --git a/rust/hyperlane-base/src/contract_sync/cursors/sequence_aware/forward.rs b/rust/hyperlane-base/src/contract_sync/cursors/sequence_aware/forward.rs index 374b4b797..7314e2a00 100644 --- a/rust/hyperlane-base/src/contract_sync/cursors/sequence_aware/forward.rs +++ b/rust/hyperlane-base/src/contract_sync/cursors/sequence_aware/forward.rs @@ -41,6 +41,18 @@ pub(crate) struct ForwardSequenceAwareSyncCursor { index_mode: IndexMode, } +impl Debug for ForwardSequenceAwareSyncCursor { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ForwardSequenceAwareSyncCursor") + .field("chunk_size", &self.chunk_size) + .field("last_indexed_snapshot", &self.last_indexed_snapshot) + .field("current_indexing_snapshot", &self.current_indexing_snapshot) + .field("target_snapshot", &self.target_snapshot) + .field("index_mode", &self.index_mode) + .finish() + } +} + impl ForwardSequenceAwareSyncCursor { #[instrument( skip(db, latest_sequence_querier), @@ -391,18 +403,6 @@ impl ForwardSequenceAwareSyncCursor { } } -impl Debug for ForwardSequenceAwareSyncCursor { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("ForwardSequenceAwareSyncCursor") - .field("chunk_size", &self.chunk_size) - .field("current_indexing_snapshot", &self.current_indexing_snapshot) - .field("last_indexed_snapshot", &self.last_indexed_snapshot) - .field("target_snapshot", &self.target_snapshot) - .field("index_mode", &self.index_mode) - .finish() - } -} - #[async_trait] impl ContractSyncCursor for ForwardSequenceAwareSyncCursor @@ -493,7 +493,7 @@ pub(crate) mod test { where T: Sequenced + Debug, { - async fn fetch_logs( + async fn fetch_logs_in_range( &self, _range: RangeInclusive, ) -> ChainResult, LogMeta)>> { diff --git a/rust/hyperlane-base/src/contract_sync/cursors/sequence_aware/mod.rs b/rust/hyperlane-base/src/contract_sync/cursors/sequence_aware/mod.rs index d3abb4384..9303438b0 100644 --- a/rust/hyperlane-base/src/contract_sync/cursors/sequence_aware/mod.rs +++ b/rust/hyperlane-base/src/contract_sync/cursors/sequence_aware/mod.rs @@ -62,6 +62,7 @@ pub enum SyncDirection { /// A cursor that prefers to sync forward, but will sync backward if there is nothing to /// sync forward. +#[derive(Debug)] pub(crate) struct ForwardBackwardSequenceAwareSyncCursor { forward: ForwardSequenceAwareSyncCursor, backward: BackwardSequenceAwareSyncCursor, diff --git a/rust/hyperlane-base/src/contract_sync/mod.rs b/rust/hyperlane-base/src/contract_sync/mod.rs index b97e3e5f4..9c8ba75d6 100644 --- a/rust/hyperlane-base/src/contract_sync/mod.rs +++ b/rust/hyperlane-base/src/contract_sync/mod.rs @@ -10,9 +10,13 @@ use hyperlane_core::{ HyperlaneSequenceAwareIndexerStore, HyperlaneWatermarkedLogStore, Indexer, SequenceAwareIndexer, }; +use hyperlane_core::{Indexed, LogMeta, H512}; pub use metrics::ContractSyncMetrics; +use prometheus::core::{AtomicI64, AtomicU64, GenericCounter, GenericGauge}; +use tokio::sync::broadcast::error::TryRecvError; +use tokio::sync::broadcast::{Receiver as BroadcastReceiver, Sender as BroadcastSender}; use tokio::time::sleep; -use tracing::{debug, info, warn}; +use tracing::{debug, info, instrument, trace, warn}; use crate::settings::IndexSettings; @@ -27,17 +31,33 @@ const SLEEP_DURATION: Duration = Duration::from_secs(5); /// Entity that drives the syncing of an agent's db with on-chain data. /// Extracts chain-specific data (emitted checkpoints, messages, etc) from an /// `indexer` and fills the agent's db with this data. -#[derive(Debug, new, Clone)] -pub struct ContractSync, I: Indexer> { +#[derive(Debug)] +pub struct ContractSync, I: Indexer> { domain: HyperlaneDomain, db: D, indexer: I, metrics: ContractSyncMetrics, + broadcast_sender: Option>, _phantom: PhantomData, } +impl, I: Indexer> ContractSync { + /// Create a new ContractSync + pub fn new(domain: HyperlaneDomain, db: D, indexer: I, metrics: ContractSyncMetrics) -> Self { + Self { + domain, + db, + indexer, + metrics, + broadcast_sender: T::broadcast_channel_size().map(BroadcastSender::new), + _phantom: PhantomData, + } + } +} + impl ContractSync where + T: Indexable + Debug + Send + Sync + Clone + Eq + Hash + 'static, D: HyperlaneLogStore, I: Indexer + 'static, { @@ -45,82 +65,161 @@ where pub fn domain(&self) -> &HyperlaneDomain { &self.domain } -} -impl ContractSync -where - T: Debug + Send + Sync + Clone + Eq + Hash + 'static, - D: HyperlaneLogStore, - I: Indexer + 'static, -{ + fn get_broadcaster(&self) -> Option> { + self.broadcast_sender.clone() + } + /// Sync logs and write them to the LogStore - #[tracing::instrument(name = "ContractSync", fields(domain=self.domain().name()), skip(self, cursor))] - pub async fn sync(&self, label: &'static str, mut cursor: Box>) { + #[instrument(name = "ContractSync", fields(domain=self.domain().name()), skip(self, opts))] + pub async fn sync(&self, label: &'static str, mut opts: SyncOptions) { let chain_name = self.domain.as_ref(); - let indexed_height = self + let indexed_height_metric = self .metrics .indexed_height .with_label_values(&[label, chain_name]); - let stored_logs = self + let stored_logs_metric = self .metrics .stored_events .with_label_values(&[label, chain_name]); loop { - indexed_height.set(cursor.latest_queried_block() as i64); + if let Some(rx) = opts.tx_id_receiver.as_mut() { + self.fetch_logs_from_receiver(rx, &stored_logs_metric).await; + } + if let Some(cursor) = opts.cursor.as_mut() { + self.fetch_logs_with_cursor(cursor, &stored_logs_metric, &indexed_height_metric) + .await; + } + } + } - let (action, eta) = match cursor.next_action().await { - Ok((action, eta)) => (action, eta), - Err(err) => { - warn!(?err, "Error getting next action"); - sleep(SLEEP_DURATION).await; - continue; - } - }; - let sleep_duration = match action { - // Use `loop` but always break - this allows for returning a value - // from the loop (the sleep duration) - #[allow(clippy::never_loop)] - CursorAction::Query(range) => loop { - debug!(?range, "Looking for for events in index range"); - - let logs = match self.indexer.fetch_logs(range.clone()).await { + #[instrument(fields(domain=self.domain().name()), skip(self, recv, stored_logs_metric))] + async fn fetch_logs_from_receiver( + &self, + recv: &mut BroadcastReceiver, + stored_logs_metric: &GenericCounter, + ) { + loop { + match recv.try_recv() { + Ok(tx_id) => { + let logs = match self.indexer.fetch_logs_by_tx_hash(tx_id).await { Ok(logs) => logs, Err(err) => { - warn!(?err, "Error fetching logs"); - break SLEEP_DURATION; + warn!(?err, ?tx_id, "Error fetching logs for tx id"); + continue; } }; - let deduped_logs = HashSet::<_>::from_iter(logs); - let logs = Vec::from_iter(deduped_logs); - + let logs = self.dedupe_and_store_logs(logs, stored_logs_metric).await; + let num_logs = logs.len() as u64; info!( - ?range, - num_logs = logs.len(), - estimated_time_to_sync = fmt_sync_time(eta), - "Found log(s) in index range" + num_logs, + ?tx_id, + sequences = ?logs.iter().map(|(log, _)| log.sequence).collect::>(), + "Found log(s) for tx id" ); - // Store deliveries - let stored = match self.db.store_logs(&logs).await { - Ok(stored) => stored, - Err(err) => { - warn!(?err, "Error storing logs in db"); - break SLEEP_DURATION; - } - }; - // Report amount of deliveries stored into db - stored_logs.inc_by(stored as u64); - // Update cursor - if let Err(err) = cursor.update(logs, range).await { - warn!(?err, "Error updating cursor"); + } + Err(TryRecvError::Empty) => { + trace!("No txid received"); + break; + } + Err(err) => { + warn!(?err, "Error receiving txid from channel"); + break; + } + } + } + } + + #[instrument(fields(domain=self.domain().name()), skip(self, stored_logs_metric, indexed_height_metric))] + async fn fetch_logs_with_cursor( + &self, + cursor: &mut Box>, + stored_logs_metric: &GenericCounter, + indexed_height_metric: &GenericGauge, + ) { + indexed_height_metric.set(cursor.latest_queried_block() as i64); + let (action, eta) = match cursor.next_action().await { + Ok((action, eta)) => (action, eta), + Err(err) => { + warn!(?err, "Error getting next action"); + sleep(SLEEP_DURATION).await; + return; + } + }; + let sleep_duration = match action { + // Use `loop` but always break - this allows for returning a value + // from the loop (the sleep duration) + #[allow(clippy::never_loop)] + CursorAction::Query(range) => loop { + debug!(?range, "Looking for events in index range"); + + let logs = match self.indexer.fetch_logs_in_range(range.clone()).await { + Ok(logs) => logs, + Err(err) => { + warn!(?err, ?range, "Error fetching logs in range"); break SLEEP_DURATION; - }; - break Default::default(); - }, - CursorAction::Sleep(duration) => duration, - }; - sleep(sleep_duration).await; + } + }; + + let logs = self.dedupe_and_store_logs(logs, stored_logs_metric).await; + let logs_found = logs.len() as u64; + info!( + ?range, + num_logs = logs_found, + estimated_time_to_sync = fmt_sync_time(eta), + sequences = ?logs.iter().map(|(log, _)| log.sequence).collect::>(), + cursor = ?cursor, + "Found log(s) in index range" + ); + + if let Some(tx) = self.broadcast_sender.as_ref() { + logs.iter().for_each(|(_, meta)| { + if let Err(err) = tx.send(meta.transaction_id) { + trace!(?err, "Error sending txid to receiver"); + } + }); + } + + // Update cursor + if let Err(err) = cursor.update(logs, range).await { + warn!(?err, "Error updating cursor"); + break SLEEP_DURATION; + }; + break Default::default(); + }, + CursorAction::Sleep(duration) => duration, + }; + sleep(sleep_duration).await + } + + async fn dedupe_and_store_logs( + &self, + logs: Vec<(Indexed, LogMeta)>, + stored_logs_metric: &GenericCounter, + ) -> Vec<(Indexed, LogMeta)> { + let deduped_logs = HashSet::<_>::from_iter(logs); + let logs = Vec::from_iter(deduped_logs); + + // Store deliveries + let stored = match self.db.store_logs(&logs).await { + Ok(stored) => stored, + Err(err) => { + warn!(?err, "Error storing logs in db"); + Default::default() + } + }; + if stored > 0 { + debug!( + domain = self.domain.as_ref(), + count = stored, + sequences = ?logs.iter().map(|(log, _)| log.sequence).collect::>(), + "Stored logs in db", + ); } + // Report amount of deliveries stored into db + stored_logs_metric.inc_by(stored as u64); + logs } } @@ -141,16 +240,38 @@ pub trait ContractSyncer: Send + Sync { async fn cursor(&self, index_settings: IndexSettings) -> Box>; /// Syncs events from the indexer using the provided cursor - async fn sync(&self, label: &'static str, cursor: Box>); + async fn sync(&self, label: &'static str, opts: SyncOptions); /// The domain of this syncer fn domain(&self) -> &HyperlaneDomain; + + /// If this syncer is also a broadcaster, return the channel to receive txids + fn get_broadcaster(&self) -> Option>; +} + +#[derive(new)] +/// Options for syncing events +pub struct SyncOptions { + // Keep as optional fields for now to run them simultaneously. + // Might want to refactor into an enum later, where we either index with a cursor or rely on receiving + // txids from a channel to other indexing tasks + cursor: Option>>, + tx_id_receiver: Option>, +} + +impl From>> for SyncOptions { + fn from(cursor: Box>) -> Self { + Self { + cursor: Some(cursor), + tx_id_receiver: None, + } + } } #[async_trait] impl ContractSyncer for WatermarkContractSync where - T: Debug + Send + Sync + Clone + Eq + Hash + 'static, + T: Indexable + Debug + Send + Sync + Clone + Eq + Hash + 'static, { /// Returns a new cursor to be used for syncing events from the indexer based on time async fn cursor(&self, index_settings: IndexSettings) -> Box> { @@ -172,13 +293,17 @@ where ) } - async fn sync(&self, label: &'static str, cursor: Box>) { - ContractSync::sync(self, label, cursor).await; + async fn sync(&self, label: &'static str, opts: SyncOptions) { + ContractSync::sync(self, label, opts).await } fn domain(&self) -> &HyperlaneDomain { ContractSync::domain(self) } + + fn get_broadcaster(&self) -> Option> { + ContractSync::get_broadcaster(self) + } } /// Log store for sequence aware cursors @@ -191,7 +316,7 @@ pub type SequencedDataContractSync = #[async_trait] impl ContractSyncer for SequencedDataContractSync where - T: Send + Sync + Debug + Clone + Eq + Hash + 'static, + T: Indexable + Send + Sync + Debug + Clone + Eq + Hash + 'static, { /// Returns a new cursor to be used for syncing dispatched messages from the indexer async fn cursor(&self, index_settings: IndexSettings) -> Box> { @@ -207,11 +332,15 @@ where ) } - async fn sync(&self, label: &'static str, cursor: Box>) { - ContractSync::sync(self, label, cursor).await; + async fn sync(&self, label: &'static str, opts: SyncOptions) { + ContractSync::sync(self, label, opts).await; } fn domain(&self) -> &HyperlaneDomain { ContractSync::domain(self) } + + fn get_broadcaster(&self) -> Option> { + ContractSync::get_broadcaster(self) + } } diff --git a/rust/hyperlane-base/src/db/rocks/hyperlane_db.rs b/rust/hyperlane-base/src/db/rocks/hyperlane_db.rs index 3d164ce26..b4323613a 100644 --- a/rust/hyperlane-base/src/db/rocks/hyperlane_db.rs +++ b/rust/hyperlane-base/src/db/rocks/hyperlane_db.rs @@ -242,10 +242,10 @@ impl HyperlaneRocksDB { &self, event: InterchainGasExpenditure, ) -> DbResult<()> { - let existing_payment = self.retrieve_gas_expenditure_by_message_id(event.message_id)?; - let total = existing_payment + event; + let existing_expenditure = self.retrieve_gas_expenditure_by_message_id(event.message_id)?; + let total = existing_expenditure + event; - debug!(?event, new_total_gas_payment=?total, "Storing gas payment"); + debug!(?event, new_total_gas_expenditure=?total, "Storing gas expenditure"); self.store_interchain_gas_expenditure_data_by_message_id( &total.message_id, &InterchainGasExpenditureData { diff --git a/rust/hyperlane-base/src/settings/base.rs b/rust/hyperlane-base/src/settings/base.rs index 59b8fa11a..6757a545e 100644 --- a/rust/hyperlane-base/src/settings/base.rs +++ b/rust/hyperlane-base/src/settings/base.rs @@ -160,7 +160,7 @@ impl Settings { db: Arc, ) -> eyre::Result>> where - T: Debug, + T: Indexable + Debug, SequenceIndexer: TryFromWithMetrics, D: HyperlaneLogStore + HyperlaneSequenceAwareIndexerStoreReader + 'static, { @@ -184,7 +184,7 @@ impl Settings { db: Arc, ) -> eyre::Result>> where - T: Debug, + T: Indexable + Debug, SequenceIndexer: TryFromWithMetrics, D: HyperlaneLogStore + HyperlaneWatermarkedLogStore + 'static, { diff --git a/rust/hyperlane-base/src/settings/mod.rs b/rust/hyperlane-base/src/settings/mod.rs index e80944432..aa7bee534 100644 --- a/rust/hyperlane-base/src/settings/mod.rs +++ b/rust/hyperlane-base/src/settings/mod.rs @@ -1,6 +1,6 @@ //! Common settings and configuration for Hyperlane agents //! -//! The correct settings shape is defined in the TypeScript SDK metadata. While the the exact shape +//! The correct settings shape is defined in the TypeScript SDK metadata. While the exact shape //! and validations it defines are not applied here, we should mirror them. //! ANY CHANGES HERE NEED TO BE REFLECTED IN THE TYPESCRIPT SDK. //! diff --git a/rust/hyperlane-base/src/settings/parser/mod.rs b/rust/hyperlane-base/src/settings/parser/mod.rs index 30430e04e..3c3a3aa65 100644 --- a/rust/hyperlane-base/src/settings/parser/mod.rs +++ b/rust/hyperlane-base/src/settings/parser/mod.rs @@ -1,6 +1,6 @@ //! This module is responsible for parsing the agent's settings. //! -//! The correct settings shape is defined in the TypeScript SDK metadata. While the the exact shape +//! The correct settings shape is defined in the TypeScript SDK metadata. While the exact shape //! and validations it defines are not applied here, we should mirror them. //! ANY CHANGES HERE NEED TO BE REFLECTED IN THE TYPESCRIPT SDK. diff --git a/rust/hyperlane-core/Cargo.toml b/rust/hyperlane-core/Cargo.toml index 5f34bc209..21ee23235 100644 --- a/rust/hyperlane-core/Cargo.toml +++ b/rust/hyperlane-core/Cargo.toml @@ -49,7 +49,7 @@ uint.workspace = true tokio = { workspace = true, features = ["rt", "time"] } [features] -default = [] +default = ["strum"] float = [] test-utils = ["dep:config"] agent = ["ethers", "strum"] diff --git a/rust/hyperlane-core/src/chain.rs b/rust/hyperlane-core/src/chain.rs index 667d392ad..fe32b8630 100644 --- a/rust/hyperlane-core/src/chain.rs +++ b/rust/hyperlane-core/src/chain.rs @@ -51,6 +51,7 @@ impl<'a> std::fmt::Display for ContractLocator<'a> { pub enum KnownHyperlaneDomain { Ethereum = 1, Sepolia = 11155111, + Holesky = 17000, Polygon = 137, @@ -218,7 +219,7 @@ impl KnownHyperlaneDomain { Moonbeam, Gnosis, MantaPacific, Neutron, Injective, InEvm ], Testnet: [ - Alfajores, MoonbaseAlpha, Sepolia, ScrollSepolia, Chiado, PlumeTestnet, Fuji, BinanceSmartChainTestnet + Alfajores, MoonbaseAlpha, Sepolia, ScrollSepolia, Chiado, PlumeTestnet, Fuji, BinanceSmartChainTestnet, Holesky ], LocalTestChain: [Test1, Test2, Test3, FuelTest1, SealevelTest1, SealevelTest2, CosmosTest99990, CosmosTest99991], }) @@ -229,7 +230,7 @@ impl KnownHyperlaneDomain { many_to_one!(match self { HyperlaneDomainProtocol::Ethereum: [ - Ethereum, Sepolia, Polygon, Avalanche, Fuji, Arbitrum, + Ethereum, Sepolia, Holesky, Polygon, Avalanche, Fuji, Arbitrum, Optimism, BinanceSmartChain, BinanceSmartChainTestnet, Celo, Gnosis, Alfajores, Moonbeam, InEvm, MoonbaseAlpha, ScrollSepolia, Chiado, MantaPacific, PlumeTestnet, Test1, Test2, Test3 @@ -246,7 +247,7 @@ impl KnownHyperlaneDomain { many_to_one!(match self { HyperlaneDomainTechnicalStack::ArbitrumNitro: [Arbitrum, PlumeTestnet], HyperlaneDomainTechnicalStack::Other: [ - Ethereum, Sepolia, Polygon, Avalanche, Fuji, Optimism, + Ethereum, Sepolia, Holesky, Polygon, Avalanche, Fuji, Optimism, BinanceSmartChain, BinanceSmartChainTestnet, Celo, Gnosis, Alfajores, Moonbeam, MoonbaseAlpha, ScrollSepolia, Chiado, MantaPacific, Neutron, Injective, InEvm, Test1, Test2, Test3, FuelTest1, SealevelTest1, SealevelTest2, CosmosTest99990, CosmosTest99991 diff --git a/rust/hyperlane-core/src/traits/cursor.rs b/rust/hyperlane-core/src/traits/cursor.rs index cfe92b8dc..b835b94df 100644 --- a/rust/hyperlane-core/src/traits/cursor.rs +++ b/rust/hyperlane-core/src/traits/cursor.rs @@ -1,4 +1,8 @@ -use std::{fmt, ops::RangeInclusive, time::Duration}; +use std::{ + fmt::{self, Debug}, + ops::RangeInclusive, + time::Duration, +}; use async_trait::async_trait; use auto_impl::auto_impl; @@ -9,7 +13,7 @@ use crate::{Indexed, LogMeta}; /// A cursor governs event indexing for a contract. #[async_trait] #[auto_impl(Box)] -pub trait ContractSyncCursor: Send + Sync + 'static { +pub trait ContractSyncCursor: Debug + Send + Sync + 'static { /// The next block range that should be queried. /// This method should be tolerant to being called multiple times in a row /// without any updates in between. diff --git a/rust/hyperlane-core/src/traits/indexer.rs b/rust/hyperlane-core/src/traits/indexer.rs index 3db7e4f57..1c05360ff 100644 --- a/rust/hyperlane-core/src/traits/indexer.rs +++ b/rust/hyperlane-core/src/traits/indexer.rs @@ -11,7 +11,7 @@ use async_trait::async_trait; use auto_impl::auto_impl; use serde::Deserialize; -use crate::{ChainResult, Indexed, LogMeta}; +use crate::{ChainResult, Indexed, LogMeta, H512}; /// Indexing mode. #[derive(Copy, Debug, Default, Deserialize, Clone)] @@ -29,13 +29,21 @@ pub enum IndexMode { #[auto_impl(&, Box, Arc,)] pub trait Indexer: Send + Sync + Debug { /// Fetch list of logs between blocks `from` and `to`, inclusive. - async fn fetch_logs( + async fn fetch_logs_in_range( &self, range: RangeInclusive, ) -> ChainResult, LogMeta)>>; /// Get the chain's latest block number that has reached finality async fn get_finalized_block_number(&self) -> ChainResult; + + /// Fetch list of logs emitted in a transaction with the given hash. + async fn fetch_logs_by_tx_hash( + &self, + _tx_hash: H512, + ) -> ChainResult, LogMeta)>> { + Ok(vec![]) + } } /// Interface for indexing data in sequence. diff --git a/rust/hyperlane-core/src/traits/mod.rs b/rust/hyperlane-core/src/traits/mod.rs index e85b04f4a..b168a1892 100644 --- a/rust/hyperlane-core/src/traits/mod.rs +++ b/rust/hyperlane-core/src/traits/mod.rs @@ -10,6 +10,7 @@ pub use interchain_security_module::*; pub use mailbox::*; pub use merkle_tree_hook::*; pub use multisig_ism::*; +pub use pending_operation::*; pub use provider::*; pub use routing_ism::*; pub use signing::*; @@ -29,6 +30,7 @@ mod interchain_security_module; mod mailbox; mod merkle_tree_hook; mod multisig_ism; +mod pending_operation; mod provider; mod routing_ism; mod signing; diff --git a/rust/agents/relayer/src/msg/pending_operation.rs b/rust/hyperlane-core/src/traits/pending_operation.rs similarity index 75% rename from rust/agents/relayer/src/msg/pending_operation.rs rename to rust/hyperlane-core/src/traits/pending_operation.rs index 206e062e2..c6d494467 100644 --- a/rust/agents/relayer/src/msg/pending_operation.rs +++ b/rust/hyperlane-core/src/traits/pending_operation.rs @@ -4,10 +4,16 @@ use std::{ time::{Duration, Instant}, }; +use crate::{ + ChainResult, FixedPointNumber, HyperlaneDomain, HyperlaneMessage, TryBatchAs, TxOutcome, H256, + U256, +}; use async_trait::async_trait; -use hyperlane_core::{HyperlaneDomain, HyperlaneMessage, TryBatchAs, TxOutcome, H256}; +use num::CheckedDiv; +use tracing::warn; -use super::op_queue::QueueOperation; +/// Boxed operation that can be stored in an operation queue +pub type QueueOperation = Box; /// A pending operation that will be run by the submitter and cause a /// transaction to be sent. @@ -67,11 +73,21 @@ pub trait PendingOperation: Send + Sync + Debug + TryBatchAs { /// Set the outcome of the `submit` call fn set_submission_outcome(&mut self, outcome: TxOutcome); + /// Get the estimated the cost of the `submit` call + fn get_tx_cost_estimate(&self) -> Option; + /// This will be called after the operation has been submitted and is /// responsible for checking if the operation has reached a point at /// which we consider it safe from reorgs. async fn confirm(&mut self) -> PendingOperationResult; + /// Record the outcome of the operation + fn set_operation_outcome( + &mut self, + submission_outcome: TxOutcome, + submission_estimated_cost: U256, + ); + /// Get the earliest instant at which this should next be attempted. /// /// This is only used for sorting, the functions are responsible for @@ -85,11 +101,41 @@ pub trait PendingOperation: Send + Sync + Debug + TryBatchAs { /// retried immediately. fn reset_attempts(&mut self); - #[cfg(test)] /// Set the number of times this operation has been retried. + #[cfg(any(test, feature = "test-utils"))] fn set_retries(&mut self, retries: u32); } +/// Utility fn to calculate the total estimated cost of an operation batch +pub fn total_estimated_cost(ops: &[Box]) -> U256 { + ops.iter() + .fold(U256::zero(), |acc, op| match op.get_tx_cost_estimate() { + Some(cost_estimate) => acc.saturating_add(cost_estimate), + None => { + warn!(operation=?op, "No cost estimate available for operation, defaulting to 0"); + acc + } + }) +} + +/// Calculate the gas used by an operation (either in a batch or single-submission), by looking at the total cost of the tx, +/// and the estimated cost of the operation compared to the sum of the estimates of all operations in the batch. +/// When using this for single-submission rather than a batch, +/// the `tx_estimated_cost` should be the same as the `tx_estimated_cost` +pub fn gas_used_by_operation( + tx_outcome: &TxOutcome, + tx_estimated_cost: U256, + operation_estimated_cost: U256, +) -> ChainResult { + let gas_used_by_tx = FixedPointNumber::try_from(tx_outcome.gas_used)?; + let operation_gas_estimate = FixedPointNumber::try_from(operation_estimated_cost)?; + let tx_gas_estimate = FixedPointNumber::try_from(tx_estimated_cost)?; + let gas_used_by_operation = (gas_used_by_tx * operation_gas_estimate) + .checked_div(&tx_gas_estimate) + .ok_or(eyre::eyre!("Division by zero"))?; + gas_used_by_operation.try_into() +} + impl Display for QueueOperation { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( @@ -138,6 +184,7 @@ impl Ord for QueueOperation { } } +/// Possible outcomes of performing an action on a pending operation (such as `prepare`, `submit` or `confirm`). #[derive(Debug)] pub enum PendingOperationResult { /// Promote to the next step @@ -153,6 +200,7 @@ pub enum PendingOperationResult { } /// create a `op_try!` macro for the `on_retry` handler. +#[macro_export] macro_rules! make_op_try { ($on_retry:expr) => { /// Handle a result and either return early with retry or a critical failure on @@ -181,5 +229,3 @@ macro_rules! make_op_try { } }; } - -pub(super) use make_op_try; diff --git a/rust/hyperlane-core/src/types/channel.rs b/rust/hyperlane-core/src/types/channel.rs deleted file mode 100644 index 2a0bbb897..000000000 --- a/rust/hyperlane-core/src/types/channel.rs +++ /dev/null @@ -1,50 +0,0 @@ -use derive_new::new; -use tokio::sync::broadcast::{Receiver, Sender}; - -/// Multi-producer, multi-consumer channel -pub struct MpmcChannel { - sender: Sender, - receiver: MpmcReceiver, -} - -impl MpmcChannel { - /// Creates a new `MpmcChannel` with the specified capacity. - /// - /// # Arguments - /// - /// * `capacity` - The maximum number of messages that can be buffered in the channel. - pub fn new(capacity: usize) -> Self { - let (sender, receiver) = tokio::sync::broadcast::channel(capacity); - Self { - sender: sender.clone(), - receiver: MpmcReceiver::new(sender, receiver), - } - } - - /// Returns a clone of the sender end of the channel. - pub fn sender(&self) -> Sender { - self.sender.clone() - } - - /// Returns a clone of the receiver end of the channel. - pub fn receiver(&self) -> MpmcReceiver { - self.receiver.clone() - } -} - -/// Clonable receiving end of a multi-producer, multi-consumer channel -#[derive(Debug, new)] -pub struct MpmcReceiver { - sender: Sender, - /// The receiving end of the channel. - pub receiver: Receiver, -} - -impl Clone for MpmcReceiver { - fn clone(&self) -> Self { - Self { - sender: self.sender.clone(), - receiver: self.sender.subscribe(), - } - } -} diff --git a/rust/hyperlane-core/src/types/mod.rs b/rust/hyperlane-core/src/types/mod.rs index 59f20630b..c8b2ad346 100644 --- a/rust/hyperlane-core/src/types/mod.rs +++ b/rust/hyperlane-core/src/types/mod.rs @@ -8,8 +8,6 @@ pub use self::primitive_types::*; pub use ::primitive_types as ethers_core_types; pub use announcement::*; pub use chain_data::*; -#[cfg(feature = "async")] -pub use channel::*; pub use checkpoint::*; pub use indexing::*; pub use log_metadata::*; @@ -21,8 +19,6 @@ use crate::{Decode, Encode, HyperlaneProtocolError}; mod announcement; mod chain_data; -#[cfg(feature = "async")] -mod channel; mod checkpoint; mod indexing; mod log_metadata; diff --git a/rust/hyperlane-core/src/types/primitive_types.rs b/rust/hyperlane-core/src/types/primitive_types.rs index 2a3c53d40..c5636b3b9 100644 --- a/rust/hyperlane-core/src/types/primitive_types.rs +++ b/rust/hyperlane-core/src/types/primitive_types.rs @@ -3,11 +3,15 @@ #![allow(clippy::assign_op_pattern)] #![allow(clippy::reversed_empty_ranges)] -use std::{ops::Mul, str::FromStr}; +use std::{ + ops::{Div, Mul}, + str::FromStr, +}; use bigdecimal::{BigDecimal, RoundingMode}; use borsh::{BorshDeserialize, BorshSerialize}; use fixed_hash::impl_fixed_hash_conversions; +use num::CheckedDiv; use num_traits::Zero; use uint::construct_uint; @@ -421,6 +425,27 @@ where } } +impl Div for FixedPointNumber +where + T: Into, +{ + type Output = FixedPointNumber; + + fn div(self, rhs: T) -> Self::Output { + let rhs = rhs.into(); + Self(self.0 / rhs.0) + } +} + +impl CheckedDiv for FixedPointNumber { + fn checked_div(&self, v: &Self) -> Option { + if v.0.is_zero() { + return None; + } + Some(Self(self.0.clone() / v.0.clone())) + } +} + impl FromStr for FixedPointNumber { type Err = ChainCommunicationError; diff --git a/rust/utils/backtrace-oneline/src/lib.rs b/rust/utils/backtrace-oneline/src/lib.rs index 0c69ee374..61261f11d 100644 --- a/rust/utils/backtrace-oneline/src/lib.rs +++ b/rust/utils/backtrace-oneline/src/lib.rs @@ -118,7 +118,7 @@ impl BacktraceFrameFmt<'_, '_, '_> { symbol.name(), // TODO: this isn't great that we don't end up printing anything // with non-utf8 filenames. Thankfully almost everything is utf8 so - // this shouldn't be too too bad. + // this shouldn't be too bad. symbol .filename() .and_then(|p| Some(BytesOrWideString::Bytes(p.to_str()?.as_bytes()))), diff --git a/rust/utils/run-locally/Cargo.toml b/rust/utils/run-locally/Cargo.toml index 45c07d030..99b0e41c9 100644 --- a/rust/utils/run-locally/Cargo.toml +++ b/rust/utils/run-locally/Cargo.toml @@ -28,11 +28,13 @@ ethers-contract.workspace = true tokio.workspace = true maplit.workspace = true nix = { workspace = true, features = ["signal"], default-features = false } +once_cell.workspace = true tempfile.workspace = true ureq = { workspace = true, default-features = false } which.workspace = true macro_rules_attribute.workspace = true regex.workspace = true +relayer = { path = "../../agents/relayer"} hyperlane-cosmwasm-interface.workspace = true cosmwasm-schema.workspace = true diff --git a/rust/utils/run-locally/src/config.rs b/rust/utils/run-locally/src/config.rs index 7e1358dfd..476a10725 100644 --- a/rust/utils/run-locally/src/config.rs +++ b/rust/utils/run-locally/src/config.rs @@ -6,6 +6,7 @@ pub struct Config { pub ci_mode: bool, pub ci_mode_timeout: u64, pub kathy_messages: u64, + pub sealevel_enabled: bool, // TODO: Include count of sealevel messages in a field separate from `kathy_messages`? } @@ -26,6 +27,9 @@ impl Config { .map(|r| r.parse::().unwrap()); r.unwrap_or(16) }, + sealevel_enabled: env::var("SEALEVEL_ENABLED") + .map(|k| k.parse::().unwrap()) + .unwrap_or(true), }) } } diff --git a/rust/utils/run-locally/src/cosmos/cli.rs b/rust/utils/run-locally/src/cosmos/cli.rs index 4258f149c..934a3758a 100644 --- a/rust/utils/run-locally/src/cosmos/cli.rs +++ b/rust/utils/run-locally/src/cosmos/cli.rs @@ -152,7 +152,7 @@ impl OsmosisCLI { .arg("grpc.address", &endpoint.grpc_addr) // default is 0.0.0.0:9090 .arg("rpc.pprof_laddr", pprof_addr) // default is localhost:6060 .arg("log_level", "panic") - .spawn("COSMOS"); + .spawn("COSMOS", None); endpoint.wait_for_node(); diff --git a/rust/utils/run-locally/src/cosmos/mod.rs b/rust/utils/run-locally/src/cosmos/mod.rs index 1a3f1e7cd..48cc117e2 100644 --- a/rust/utils/run-locally/src/cosmos/mod.rs +++ b/rust/utils/run-locally/src/cosmos/mod.rs @@ -271,7 +271,7 @@ fn launch_cosmos_validator( .hyp_env("SIGNER_SIGNER_TYPE", "hexKey") .hyp_env("SIGNER_KEY", agent_config.signer.key) .hyp_env("TRACING_LEVEL", if debug { "debug" } else { "info" }) - .spawn("VAL"); + .spawn("VAL", None); validator } @@ -299,7 +299,7 @@ fn launch_cosmos_relayer( .hyp_env("TRACING_LEVEL", if debug { "debug" } else { "info" }) .hyp_env("GASPAYMENTENFORCEMENT", "[{\"type\": \"none\"}]") .hyp_env("METRICSPORT", metrics.to_string()) - .spawn("RLY"); + .spawn("RLY", None); relayer } diff --git a/rust/utils/run-locally/src/ethereum/mod.rs b/rust/utils/run-locally/src/ethereum/mod.rs index bebe06348..acdd3057d 100644 --- a/rust/utils/run-locally/src/ethereum/mod.rs +++ b/rust/utils/run-locally/src/ethereum/mod.rs @@ -36,7 +36,7 @@ pub fn start_anvil(config: Arc) -> AgentHandles { } log!("Launching anvil..."); let anvil_args = Program::new("anvil").flag("silent").filter_logs(|_| false); // for now do not keep any of the anvil logs - let anvil = anvil_args.spawn("ETH"); + let anvil = anvil_args.spawn("ETH", None); sleep(Duration::from_secs(10)); diff --git a/rust/utils/run-locally/src/invariants.rs b/rust/utils/run-locally/src/invariants.rs index 690021046..2191f2ac8 100644 --- a/rust/utils/run-locally/src/invariants.rs +++ b/rust/utils/run-locally/src/invariants.rs @@ -1,14 +1,15 @@ -// use std::path::Path; - +use std::fs::File; use std::path::Path; use crate::config::Config; use crate::metrics::agent_balance_sum; +use crate::utils::get_matching_lines; use maplit::hashmap; +use relayer::GAS_EXPENDITURE_LOG_MESSAGE; use crate::logging::log; use crate::solana::solana_termination_invariants_met; -use crate::{fetch_metric, ZERO_MERKLE_INSERTION_KATHY_MESSAGES}; +use crate::{fetch_metric, AGENT_LOGGING_DIR, ZERO_MERKLE_INSERTION_KATHY_MESSAGES}; // This number should be even, so the messages can be split into two equal halves // sent before and after the relayer spins up, to avoid rounding errors. @@ -19,11 +20,16 @@ pub const SOL_MESSAGES_EXPECTED: u32 = 20; pub fn termination_invariants_met( config: &Config, starting_relayer_balance: f64, - solana_cli_tools_path: &Path, - solana_config_path: &Path, + solana_cli_tools_path: Option<&Path>, + solana_config_path: Option<&Path>, ) -> eyre::Result { let eth_messages_expected = (config.kathy_messages / 2) as u32 * 2; - let total_messages_expected = eth_messages_expected + SOL_MESSAGES_EXPECTED; + let sol_messages_expected = if config.sealevel_enabled { + SOL_MESSAGES_EXPECTED + } else { + 0 + }; + let total_messages_expected = eth_messages_expected + sol_messages_expected; let lengths = fetch_metric("9092", "hyperlane_submitter_queue_length", &hashmap! {})?; assert!(!lengths.is_empty(), "Could not find queue length metric"); @@ -55,6 +61,19 @@ pub fn termination_invariants_met( .iter() .sum::(); + let log_file_path = AGENT_LOGGING_DIR.join("RLY-output.log"); + let relayer_logfile = File::open(log_file_path)?; + let gas_expenditure_log_count = + get_matching_lines(&relayer_logfile, GAS_EXPENDITURE_LOG_MESSAGE) + .unwrap() + .len(); + + // Zero insertion messages don't reach `submit` stage where gas is spent, so we only expect these logs for the other messages. + assert_eq!( + gas_expenditure_log_count as u32, total_messages_expected, + "Didn't record gas payment for all delivered messages" + ); + let gas_payment_sealevel_events_count = fetch_metric( "9092", "hyperlane_contract_sync_stored_events", @@ -76,9 +95,13 @@ pub fn termination_invariants_met( return Ok(false); } - if !solana_termination_invariants_met(solana_cli_tools_path, solana_config_path) { - log!("Solana termination invariants not met"); - return Ok(false); + if let Some((solana_cli_tools_path, solana_config_path)) = + solana_cli_tools_path.zip(solana_config_path) + { + if !solana_termination_invariants_met(solana_cli_tools_path, solana_config_path) { + log!("Solana termination invariants not met"); + return Ok(false); + } } let dispatched_messages_scraped = fetch_metric( diff --git a/rust/utils/run-locally/src/main.rs b/rust/utils/run-locally/src/main.rs index a287b2bd1..1bf299075 100644 --- a/rust/utils/run-locally/src/main.rs +++ b/rust/utils/run-locally/src/main.rs @@ -11,12 +11,17 @@ //! the end conditions are met, the test is a failure. Defaults to 10 min. //! - `E2E_KATHY_MESSAGES`: Number of kathy messages to dispatch. Defaults to 16 if CI mode is enabled. //! else false. +//! - `SEALEVEL_ENABLED`: true/false, enables sealevel testing. Defaults to true. use std::{ - fs, + collections::HashMap, + fs::{self, File}, path::Path, process::{Child, ExitCode}, - sync::atomic::{AtomicBool, Ordering}, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, Mutex, + }, thread::sleep, time::{Duration, Instant}, }; @@ -24,6 +29,7 @@ use std::{ use ethers_contract::MULTICALL_ADDRESS; use logging::log; pub use metrics::fetch_metric; +use once_cell::sync::Lazy; use program::Program; use tempfile::tempdir; @@ -46,6 +52,12 @@ mod program; mod solana; mod utils; +pub static AGENT_LOGGING_DIR: Lazy<&Path> = Lazy::new(|| { + let dir = Path::new("/tmp/test_logs"); + fs::create_dir_all(dir).unwrap(); + dir +}); + /// These private keys are from hardhat/anvil's testing accounts. const RELAYER_KEYS: &[&str] = &[ // test1 @@ -61,17 +73,18 @@ const RELAYER_KEYS: &[&str] = &[ ]; /// These private keys are from hardhat/anvil's testing accounts. /// These must be consistent with the ISM config for the test. -const VALIDATOR_KEYS: &[&str] = &[ +const ETH_VALIDATOR_KEYS: &[&str] = &[ // eth "0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a", "0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba", "0x92db14e403b83dfe3df233f83dfa3a0d7096f21ca9b0d6d6b8d88b2b4ec1564e", +]; + +const SEALEVEL_VALIDATOR_KEYS: &[&str] = &[ // sealevel "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d", ]; -const VALIDATOR_ORIGIN_CHAINS: &[&str] = &["test1", "test2", "test3", "sealeveltest1"]; - const AGENT_BIN_PATH: &str = "target/debug"; const INFRA_PATH: &str = "../typescript/infra"; const MONOREPO_ROOT_PATH: &str = "../"; @@ -87,14 +100,15 @@ static SHUTDOWN: AtomicBool = AtomicBool::new(false); /// cleanup purposes at this time. #[derive(Default)] struct State { - agents: Vec<(String, Child)>, + #[allow(clippy::type_complexity)] + agents: HashMap>>)>, watchers: Vec>>, data: Vec>, } impl State { fn push_agent(&mut self, handles: AgentHandles) { - self.agents.push((handles.0, handles.1)); + self.agents.insert(handles.0, (handles.1, handles.5)); self.watchers.push(handles.2); self.watchers.push(handles.3); self.data.push(handles.4); @@ -105,9 +119,7 @@ impl Drop for State { fn drop(&mut self) { SHUTDOWN.store(true, Ordering::Relaxed); log!("Signaling children to stop..."); - // stop children in reverse order - self.agents.reverse(); - for (name, mut agent) in self.agents.drain(..) { + for (name, (mut agent, _)) in self.agents.drain() { log!("Stopping child {}", name); stop_child(&mut agent); } @@ -122,6 +134,7 @@ impl Drop for State { drop(data) } fs::remove_dir_all(SOLANA_CHECKPOINT_LOCATION).unwrap_or_default(); + fs::remove_dir_all::<&Path>(AGENT_LOGGING_DIR.as_ref()).unwrap_or_default(); } } @@ -133,20 +146,27 @@ fn main() -> ExitCode { }) .unwrap(); - assert_eq!(VALIDATOR_ORIGIN_CHAINS.len(), VALIDATOR_KEYS.len()); - const VALIDATOR_COUNT: usize = VALIDATOR_KEYS.len(); - let config = Config::load(); - - let solana_checkpoint_path = Path::new(SOLANA_CHECKPOINT_LOCATION); - fs::remove_dir_all(solana_checkpoint_path).unwrap_or_default(); - let checkpoints_dirs: Vec = (0..VALIDATOR_COUNT - 1) + let mut validator_origin_chains = ["test1", "test2", "test3"].to_vec(); + let mut validator_keys = ETH_VALIDATOR_KEYS.to_vec(); + let mut validator_count: usize = validator_keys.len(); + let mut checkpoints_dirs: Vec = (0..validator_count) .map(|_| Box::new(tempdir().unwrap()) as DynPath) - .chain([Box::new(solana_checkpoint_path) as DynPath]) .collect(); + if config.sealevel_enabled { + validator_origin_chains.push("sealeveltest1"); + let mut sealevel_keys = SEALEVEL_VALIDATOR_KEYS.to_vec(); + validator_keys.append(&mut sealevel_keys); + let solana_checkpoint_path = Path::new(SOLANA_CHECKPOINT_LOCATION); + fs::remove_dir_all(solana_checkpoint_path).unwrap_or_default(); + checkpoints_dirs.push(Box::new(solana_checkpoint_path) as DynPath); + validator_count += 1; + } + assert_eq!(validator_origin_chains.len(), validator_keys.len()); + let rocks_db_dir = tempdir().unwrap(); let relayer_db = concat_path(&rocks_db_dir, "relayer"); - let validator_dbs = (0..VALIDATOR_COUNT) + let validator_dbs = (0..validator_count) .map(|i| concat_path(&rocks_db_dir, format!("validator{i}"))) .collect::>(); @@ -200,15 +220,6 @@ fn main() -> ExitCode { r#"[{ "type": "minimum", "payment": "1", - "matchingList": [ - { - "originDomain": ["13375","13376"], - "destinationDomain": ["13375","13376"] - } - ] - }, - { - "type": "none" }]"#, ) .arg( @@ -216,11 +227,15 @@ fn main() -> ExitCode { "http://127.0.0.1:8545,http://127.0.0.1:8545,http://127.0.0.1:8545", ) // default is used for TEST3 - .arg("defaultSigner.key", RELAYER_KEYS[2]) - .arg( + .arg("defaultSigner.key", RELAYER_KEYS[2]); + let relayer_env = if config.sealevel_enabled { + relayer_env.arg( "relayChains", "test1,test2,test3,sealeveltest1,sealeveltest2", - ); + ) + } else { + relayer_env.arg("relayChains", "test1,test2,test3") + }; let base_validator_env = common_agent_env .clone() @@ -242,14 +257,14 @@ fn main() -> ExitCode { .hyp_env("INTERVAL", "5") .hyp_env("CHECKPOINTSYNCER_TYPE", "localStorage"); - let validator_envs = (0..VALIDATOR_COUNT) + let validator_envs = (0..validator_count) .map(|i| { base_validator_env .clone() .hyp_env("METRICSPORT", (9094 + i).to_string()) .hyp_env("DB", validator_dbs[i].to_str().unwrap()) - .hyp_env("ORIGINCHAINNAME", VALIDATOR_ORIGIN_CHAINS[i]) - .hyp_env("VALIDATOR_KEY", VALIDATOR_KEYS[i]) + .hyp_env("ORIGINCHAINNAME", validator_origin_chains[i]) + .hyp_env("VALIDATOR_KEY", validator_keys[i]) .hyp_env( "CHECKPOINTSYNCER_PATH", (*checkpoints_dirs[i]).as_ref().to_str().unwrap(), @@ -283,7 +298,7 @@ fn main() -> ExitCode { .join(", ") ); log!("Relayer DB in {}", relayer_db.display()); - (0..VALIDATOR_COUNT).for_each(|i| { + (0..validator_count).for_each(|i| { log!("Validator {} DB in {}", i + 1, validator_dbs[i].display()); }); @@ -291,9 +306,14 @@ fn main() -> ExitCode { // Ready to run... // - let (solana_path, solana_path_tempdir) = install_solana_cli_tools().join(); - state.data.push(Box::new(solana_path_tempdir)); - let solana_program_builder = build_solana_programs(solana_path.clone()); + let solana_paths = if config.sealevel_enabled { + let (solana_path, solana_path_tempdir) = install_solana_cli_tools().join(); + state.data.push(Box::new(solana_path_tempdir)); + let solana_program_builder = build_solana_programs(solana_path.clone()); + Some((solana_program_builder.join(), solana_path)) + } else { + None + }; // this task takes a long time in the CI so run it in parallel log!("Building rust..."); @@ -303,15 +323,18 @@ fn main() -> ExitCode { .arg("bin", "relayer") .arg("bin", "validator") .arg("bin", "scraper") - .arg("bin", "init-db") - .arg("bin", "hyperlane-sealevel-client") + .arg("bin", "init-db"); + let build_rust = if config.sealevel_enabled { + build_rust.arg("bin", "hyperlane-sealevel-client") + } else { + build_rust + }; + let build_rust = build_rust .filter_logs(|l| !l.contains("workspace-inheritance")) .run(); let start_anvil = start_anvil(config.clone()); - let solana_program_path = solana_program_builder.join(); - log!("Running postgres db..."); let postgres = Program::new("docker") .cmd("run") @@ -320,24 +343,31 @@ fn main() -> ExitCode { .arg("env", "POSTGRES_PASSWORD=47221c18c610") .arg("publish", "5432:5432") .cmd("postgres:14") - .spawn("SQL"); + .spawn("SQL", None); state.push_agent(postgres); build_rust.join(); let solana_ledger_dir = tempdir().unwrap(); - let start_solana_validator = start_solana_test_validator( - solana_path.clone(), - solana_program_path, - solana_ledger_dir.as_ref().to_path_buf(), - ); + let solana_config_path = if let Some((solana_program_path, solana_path)) = solana_paths.clone() + { + let start_solana_validator = start_solana_test_validator( + solana_path.clone(), + solana_program_path, + solana_ledger_dir.as_ref().to_path_buf(), + ); + + let (solana_config_path, solana_validator) = start_solana_validator.join(); + state.push_agent(solana_validator); + Some(solana_config_path) + } else { + None + }; - let (solana_config_path, solana_validator) = start_solana_validator.join(); - state.push_agent(solana_validator); state.push_agent(start_anvil.join()); // spawn 1st validator before any messages have been sent to test empty mailbox - state.push_agent(validator_envs.first().unwrap().clone().spawn("VL1")); + state.push_agent(validator_envs.first().unwrap().clone().spawn("VL1", None)); sleep(Duration::from_secs(5)); @@ -345,7 +375,7 @@ fn main() -> ExitCode { Program::new(concat_path(AGENT_BIN_PATH, "init-db")) .run() .join(); - state.push_agent(scraper_env.spawn("SCR")); + state.push_agent(scraper_env.spawn("SCR", None)); // Send half the kathy messages before starting the rest of the agents let kathy_env_single_insertion = Program::new("yarn") @@ -378,22 +408,35 @@ fn main() -> ExitCode { .arg("required-hook", "merkleTreeHook"); kathy_env_double_insertion.clone().run().join(); - // Send some sealevel messages before spinning up the agents, to test the backward indexing cursor - for _i in 0..(SOL_MESSAGES_EXPECTED / 2) { - initiate_solana_hyperlane_transfer(solana_path.clone(), solana_config_path.clone()).join(); + if let Some((solana_config_path, (_, solana_path))) = + solana_config_path.clone().zip(solana_paths.clone()) + { + // Send some sealevel messages before spinning up the agents, to test the backward indexing cursor + for _i in 0..(SOL_MESSAGES_EXPECTED / 2) { + initiate_solana_hyperlane_transfer(solana_path.clone(), solana_config_path.clone()) + .join(); + } } // spawn the rest of the validators for (i, validator_env) in validator_envs.into_iter().enumerate().skip(1) { - let validator = validator_env.spawn(make_static(format!("VL{}", 1 + i))); + let validator = validator_env.spawn( + make_static(format!("VL{}", 1 + i)), + Some(AGENT_LOGGING_DIR.as_ref()), + ); state.push_agent(validator); } - state.push_agent(relayer_env.spawn("RLY")); + state.push_agent(relayer_env.spawn("RLY", Some(&AGENT_LOGGING_DIR))); - // Send some sealevel messages after spinning up the relayer, to test the forward indexing cursor - for _i in 0..(SOL_MESSAGES_EXPECTED / 2) { - initiate_solana_hyperlane_transfer(solana_path.clone(), solana_config_path.clone()).join(); + if let Some((solana_config_path, (_, solana_path))) = + solana_config_path.clone().zip(solana_paths.clone()) + { + // Send some sealevel messages before spinning up the agents, to test the backward indexing cursor + for _i in 0..(SOL_MESSAGES_EXPECTED / 2) { + initiate_solana_hyperlane_transfer(solana_path.clone(), solana_config_path.clone()) + .join(); + } } log!("Setup complete! Agents running in background..."); @@ -402,7 +445,11 @@ fn main() -> ExitCode { // Send half the kathy messages after the relayer comes up kathy_env_double_insertion.clone().run().join(); kathy_env_zero_insertion.clone().run().join(); - state.push_agent(kathy_env_single_insertion.flag("mineforever").spawn("KTY")); + state.push_agent( + kathy_env_single_insertion + .flag("mineforever") + .spawn("KTY", None), + ); let loop_start = Instant::now(); // give things a chance to fully start. @@ -412,12 +459,14 @@ fn main() -> ExitCode { while !SHUTDOWN.load(Ordering::Relaxed) { if config.ci_mode { // for CI we have to look for the end condition. - // if termination_invariants_met(&config, starting_relayer_balance) if termination_invariants_met( &config, starting_relayer_balance, - &solana_path, - &solana_config_path, + solana_paths + .clone() + .map(|(_, solana_path)| solana_path) + .as_deref(), + solana_config_path.as_deref(), ) .unwrap_or(false) { @@ -432,7 +481,7 @@ fn main() -> ExitCode { } // verify long-running tasks are still running - for (name, child) in state.agents.iter_mut() { + for (name, (child, _)) in state.agents.iter_mut() { if let Some(status) = child.try_wait().unwrap() { if !status.success() { log!( diff --git a/rust/utils/run-locally/src/program.rs b/rust/utils/run-locally/src/program.rs index 5c2768ae1..3775ef8e9 100644 --- a/rust/utils/run-locally/src/program.rs +++ b/rust/utils/run-locally/src/program.rs @@ -2,14 +2,14 @@ use std::{ collections::BTreeMap, ffi::OsStr, fmt::{Debug, Display, Formatter}, - io::{BufRead, BufReader, Read}, + fs::{File, OpenOptions}, + io::{BufRead, BufReader, Read, Write}, path::{Path, PathBuf}, process::{Command, Stdio}, sync::{ atomic::{AtomicBool, Ordering}, - mpsc, - mpsc::Sender, - Arc, + mpsc::{self, Sender}, + Arc, Mutex, }, thread::{sleep, spawn}, time::Duration, @@ -240,8 +240,18 @@ impl Program { }) } - pub fn spawn(self, log_prefix: &'static str) -> AgentHandles { + pub fn spawn(self, log_prefix: &'static str, logs_dir: Option<&Path>) -> AgentHandles { let mut command = self.create_command(); + let log_file = logs_dir.map(|logs_dir| { + let log_file_name = format!("{}-output.log", log_prefix); + let log_file_path = logs_dir.join(log_file_name); + let log_file = OpenOptions::new() + .append(true) + .create(true) + .open(log_file_path) + .expect("Failed to create a log file"); + Arc::new(Mutex::new(log_file)) + }); command.stdout(Stdio::piped()).stderr(Stdio::piped()); log!("Spawning {}...", &self); @@ -250,17 +260,35 @@ impl Program { .unwrap_or_else(|e| panic!("Failed to start {:?} with error: {e}", &self)); let child_stdout = child.stdout.take().unwrap(); let filter = self.get_filter(); - let stdout = - spawn(move || prefix_log(child_stdout, log_prefix, &RUN_LOG_WATCHERS, filter, None)); + let cloned_log_file = log_file.clone(); + let stdout = spawn(move || { + prefix_log( + child_stdout, + log_prefix, + &RUN_LOG_WATCHERS, + filter, + cloned_log_file, + None, + ) + }); let child_stderr = child.stderr.take().unwrap(); - let stderr = - spawn(move || prefix_log(child_stderr, log_prefix, &RUN_LOG_WATCHERS, filter, None)); + let stderr = spawn(move || { + prefix_log( + child_stderr, + log_prefix, + &RUN_LOG_WATCHERS, + filter, + None, + None, + ) + }); ( log_prefix.to_owned(), child, Box::new(SimpleTaskHandle(stdout)), Box::new(SimpleTaskHandle(stderr)), self.get_memory(), + log_file.clone(), ) } @@ -281,13 +309,13 @@ impl Program { let stdout = child.stdout.take().unwrap(); let name = self.get_bin_name(); let running = running.clone(); - spawn(move || prefix_log(stdout, &name, &running, filter, stdout_ch_tx)) + spawn(move || prefix_log(stdout, &name, &running, filter, None, stdout_ch_tx)) }; let stderr = { let stderr = child.stderr.take().unwrap(); let name = self.get_bin_name(); let running = running.clone(); - spawn(move || prefix_log(stderr, &name, &running, filter, None)) + spawn(move || prefix_log(stderr, &name, &running, filter, None, None)) }; let status = loop { @@ -321,6 +349,7 @@ fn prefix_log( prefix: &str, run_log_watcher: &AtomicBool, filter: Option, + file: Option>>, channel: Option>, ) { let mut reader = BufReader::new(output).lines(); @@ -340,6 +369,10 @@ fn prefix_log( } } println!("<{prefix}> {line}"); + if let Some(file) = &file { + let mut writer = file.lock().expect("Failed to acquire lock for log file"); + writeln!(writer, "{}", line).unwrap_or(()); + } if let Some(channel) = &channel { // ignore send errors channel.send(line).unwrap_or(()); diff --git a/rust/utils/run-locally/src/solana.rs b/rust/utils/run-locally/src/solana.rs index bf5b7d417..9b0fe41e4 100644 --- a/rust/utils/run-locally/src/solana.rs +++ b/rust/utils/run-locally/src/solana.rs @@ -202,7 +202,7 @@ pub fn start_solana_test_validator( concat_path(&solana_programs_path, lib).to_str().unwrap(), ); } - let validator = args.spawn("SOL"); + let validator = args.spawn("SOL", None); sleep(Duration::from_secs(5)); log!("Deploying the hyperlane programs to solana"); diff --git a/rust/utils/run-locally/src/utils.rs b/rust/utils/run-locally/src/utils.rs index 206b4bc69..531970174 100644 --- a/rust/utils/run-locally/src/utils.rs +++ b/rust/utils/run-locally/src/utils.rs @@ -1,5 +1,8 @@ +use std::fs::File; +use std::io::{self, BufRead}; use std::path::{Path, PathBuf}; use std::process::Child; +use std::sync::{Arc, Mutex}; use std::thread::JoinHandle; use nix::libc::pid_t; @@ -54,6 +57,8 @@ pub type AgentHandles = ( Box>, // data to drop once program exits Box, + // file with stdout logs + Option>>, ); pub type LogFilter = fn(&str) -> bool; @@ -112,3 +117,16 @@ pub fn stop_child(child: &mut Child) { } }; } + +pub fn get_matching_lines(file: &File, search_string: &str) -> io::Result> { + let reader = io::BufReader::new(file); + + // Read lines and collect those that contain the search string + let matching_lines: Vec = reader + .lines() + .map_while(Result::ok) + .filter(|line| line.contains(search_string)) + .collect(); + + Ok(matching_lines) +} diff --git a/solidity/CHANGELOG.md b/solidity/CHANGELOG.md index e1735ad8e..f8cee2164 100644 --- a/solidity/CHANGELOG.md +++ b/solidity/CHANGELOG.md @@ -1,5 +1,17 @@ # @hyperlane-xyz/core +## 3.13.0 + +### Minor Changes + +- babe816f8: Support xERC20 and xERC20 Lockbox in SDK and CLI +- b440d98be: Added support for registering/deregistering from the Hyperlane AVS + +### Patch Changes + +- Updated dependencies [0cf692e73] + - @hyperlane-xyz/utils@3.13.0 + ## 3.12.0 ### Patch Changes diff --git a/solidity/contracts/avs/ECDSAStakeRegistry.sol b/solidity/contracts/avs/ECDSAStakeRegistry.sol index 0a4e32a01..6148c3c4e 100644 --- a/solidity/contracts/avs/ECDSAStakeRegistry.sol +++ b/solidity/contracts/avs/ECDSAStakeRegistry.sol @@ -44,13 +44,14 @@ contract ECDSAStakeRegistry is __ECDSAStakeRegistry_init(_serviceManager, _thresholdWeight, _quorum); } - /// @notice Registers a new operator using a provided signature + /// @notice Registers a new operator using a provided signature and signing key /// @param _operatorSignature Contains the operator's signature, salt, and expiry + /// @param _signingKey The signing key to add to the operator's history function registerOperatorWithSignature( - address _operator, - ISignatureUtils.SignatureWithSaltAndExpiry memory _operatorSignature + ISignatureUtils.SignatureWithSaltAndExpiry memory _operatorSignature, + address _signingKey ) external { - _registerOperatorWithSig(_operator, _operatorSignature); + _registerOperatorWithSig(msg.sender, _operatorSignature, _signingKey); } /// @notice Deregisters an existing operator @@ -58,6 +59,18 @@ contract ECDSAStakeRegistry is _deregisterOperator(msg.sender); } + /** + * @notice Updates the signing key for an operator + * @dev Only callable by the operator themselves + * @param _newSigningKey The new signing key to set for the operator + */ + function updateOperatorSigningKey(address _newSigningKey) external { + if (!_operatorRegistered[msg.sender]) { + revert OperatorNotRegistered(); + } + _updateOperatorSigningKey(msg.sender, _newSigningKey); + } + /** * @notice Updates the StakeRegistry's view of one or more operators' stakes adding a new entry in their history of stake checkpoints, * @dev Queries stakes from the Eigenlayer core DelegationManager contract @@ -107,18 +120,18 @@ contract ECDSAStakeRegistry is /// @notice Verifies if the provided signature data is valid for the given data hash. /// @param _dataHash The hash of the data that was signed. - /// @param _signatureData Encoded signature data consisting of an array of signers, an array of signatures, and a reference block number. + /// @param _signatureData Encoded signature data consisting of an array of operators, an array of signatures, and a reference block number. /// @return The function selector that indicates the signature is valid according to ERC1271 standard. function isValidSignature( bytes32 _dataHash, bytes memory _signatureData ) external view returns (bytes4) { ( - address[] memory signers, + address[] memory operators, bytes[] memory signatures, uint32 referenceBlock ) = abi.decode(_signatureData, (address[], bytes[], uint32)); - _checkSignatures(_dataHash, signers, signatures, referenceBlock); + _checkSignatures(_dataHash, operators, signatures, referenceBlock); return IERC1271Upgradeable.isValidSignature.selector; } @@ -128,6 +141,37 @@ contract ECDSAStakeRegistry is return _quorum; } + /** + * @notice Retrieves the latest signing key for a given operator. + * @param _operator The address of the operator. + * @return The latest signing key of the operator. + */ + function getLastestOperatorSigningKey( + address _operator + ) external view returns (address) { + return address(uint160(_operatorSigningKeyHistory[_operator].latest())); + } + + /** + * @notice Retrieves the latest signing key for a given operator at a specific block number. + * @param _operator The address of the operator. + * @param _blockNumber The block number to get the operator's signing key. + * @return The signing key of the operator at the given block. + */ + function getOperatorSigningKeyAtBlock( + address _operator, + uint256 _blockNumber + ) external view returns (address) { + return + address( + uint160( + _operatorSigningKeyHistory[_operator].getAtBlock( + _blockNumber + ) + ) + ); + } + /// @notice Retrieves the last recorded weight for a given operator. /// @param _operator The address of the operator. /// @return uint256 - The latest weight of the operator. @@ -313,9 +357,11 @@ contract ECDSAStakeRegistry is /// @dev registers an operator through a provided signature /// @param _operatorSignature Contains the operator's signature, salt, and expiry + /// @param _signingKey The signing key to add to the operator's history function _registerOperatorWithSig( address _operator, - ISignatureUtils.SignatureWithSaltAndExpiry memory _operatorSignature + ISignatureUtils.SignatureWithSaltAndExpiry memory _operatorSignature, + address _signingKey ) internal virtual { if (_operatorRegistered[_operator]) { revert OperatorAlreadyRegistered(); @@ -324,6 +370,7 @@ contract ECDSAStakeRegistry is _operatorRegistered[_operator] = true; int256 delta = _updateOperatorWeight(_operator); _updateTotalWeight(delta); + _updateOperatorSigningKey(_operator, _signingKey); IServiceManager(_serviceManager).registerOperatorToAVS( _operator, _operatorSignature @@ -331,6 +378,28 @@ contract ECDSAStakeRegistry is emit OperatorRegistered(_operator, _serviceManager); } + /// @dev Internal function to update an operator's signing key + /// @param _operator The address of the operator to update the signing key for + /// @param _newSigningKey The new signing key to set for the operator + function _updateOperatorSigningKey( + address _operator, + address _newSigningKey + ) internal { + address oldSigningKey = address( + uint160(_operatorSigningKeyHistory[_operator].latest()) + ); + if (_newSigningKey == oldSigningKey) { + return; + } + _operatorSigningKeyHistory[_operator].push(uint160(_newSigningKey)); + emit SigningKeyUpdate( + _operator, + block.number, + _newSigningKey, + oldSigningKey + ); + } + /// @notice Updates the weight of an operator and returns the previous and current weights. /// @param _operator The address of the operator to update the weight of. function _updateOperatorWeight( @@ -401,30 +470,33 @@ contract ECDSAStakeRegistry is /** * @notice Common logic to verify a batch of ECDSA signatures against a hash, using either last stake weight or at a specific block. * @param _dataHash The hash of the data the signers endorsed. - * @param _signers A collection of addresses that endorsed the data hash. + * @param _operators A collection of addresses that endorsed the data hash. * @param _signatures A collection of signatures matching the signers. * @param _referenceBlock The block number for evaluating stake weight; use max uint32 for latest weight. */ function _checkSignatures( bytes32 _dataHash, - address[] memory _signers, + address[] memory _operators, bytes[] memory _signatures, uint32 _referenceBlock ) internal view { - uint256 signersLength = _signers.length; - address lastSigner; + uint256 signersLength = _operators.length; + address currentOperator; + address lastOperator; + address signer; uint256 signedWeight; _validateSignaturesLength(signersLength, _signatures.length); for (uint256 i; i < signersLength; i++) { - address currentSigner = _signers[i]; + currentOperator = _operators[i]; + signer = _getOperatorSigningKey(currentOperator, _referenceBlock); - _validateSortedSigners(lastSigner, currentSigner); - _validateSignature(currentSigner, _dataHash, _signatures[i]); + _validateSortedSigners(lastOperator, currentOperator); + _validateSignature(signer, _dataHash, _signatures[i]); - lastSigner = currentSigner; + lastOperator = currentOperator; uint256 operatorWeight = _getOperatorWeight( - currentSigner, + currentOperator, _referenceBlock ); signedWeight += operatorWeight; @@ -474,6 +546,27 @@ contract ECDSAStakeRegistry is } } + /// @notice Retrieves the operator weight for a signer, either at the last checkpoint or a specified block. + /// @param _operator The operator to query their signing key history for + /// @param _referenceBlock The block number to query the operator's weight at, or the maximum uint32 value for the last checkpoint. + /// @return The weight of the operator. + function _getOperatorSigningKey( + address _operator, + uint32 _referenceBlock + ) internal view returns (address) { + if (_referenceBlock >= block.number) { + revert InvalidReferenceBlock(); + } + return + address( + uint160( + _operatorSigningKeyHistory[_operator].getAtBlock( + _referenceBlock + ) + ) + ); + } + /// @notice Retrieves the operator weight for a signer, either at the last checkpoint or a specified block. /// @param _signer The address of the signer whose weight is returned. /// @param _referenceBlock The block number to query the operator's weight at, or the maximum uint32 value for the last checkpoint. @@ -482,11 +575,10 @@ contract ECDSAStakeRegistry is address _signer, uint32 _referenceBlock ) internal view returns (uint256) { - if (_referenceBlock == type(uint32).max) { - return _operatorWeightHistory[_signer].latest(); - } else { - return _operatorWeightHistory[_signer].getAtBlock(_referenceBlock); + if (_referenceBlock >= block.number) { + revert InvalidReferenceBlock(); } + return _operatorWeightHistory[_signer].getAtBlock(_referenceBlock); } /// @notice Retrieve the total stake weight at a specific block or the latest if not specified. @@ -496,11 +588,10 @@ contract ECDSAStakeRegistry is function _getTotalWeight( uint32 _referenceBlock ) internal view returns (uint256) { - if (_referenceBlock == type(uint32).max) { - return _totalWeightHistory.latest(); - } else { - return _totalWeightHistory.getAtBlock(_referenceBlock); + if (_referenceBlock >= block.number) { + revert InvalidReferenceBlock(); } + return _totalWeightHistory.getAtBlock(_referenceBlock); } /// @notice Retrieves the threshold stake for a given reference block. @@ -510,11 +601,10 @@ contract ECDSAStakeRegistry is function _getThresholdStake( uint32 _referenceBlock ) internal view returns (uint256) { - if (_referenceBlock == type(uint32).max) { - return _thresholdWeightHistory.latest(); - } else { - return _thresholdWeightHistory.getAtBlock(_referenceBlock); + if (_referenceBlock >= block.number) { + revert InvalidReferenceBlock(); } + return _thresholdWeightHistory.getAtBlock(_referenceBlock); } /// @notice Validates that the cumulative stake of signed messages meets or exceeds the required threshold. diff --git a/solidity/contracts/avs/ECDSAStakeRegistryStorage.sol b/solidity/contracts/avs/ECDSAStakeRegistryStorage.sol index 9e59fd8f6..28a584ca5 100644 --- a/solidity/contracts/avs/ECDSAStakeRegistryStorage.sol +++ b/solidity/contracts/avs/ECDSAStakeRegistryStorage.sol @@ -30,6 +30,10 @@ abstract contract ECDSAStakeRegistryStorage is /// @notice Defines the duration after which the stake's weight expires. uint256 internal _stakeExpiry; + /// @notice Maps an operator to their signing key history using checkpoints + mapping(address => CheckpointsUpgradeable.History) + internal _operatorSigningKeyHistory; + /// @notice Tracks the total stake history over time using checkpoints CheckpointsUpgradeable.History internal _totalWeightHistory; @@ -51,5 +55,5 @@ abstract contract ECDSAStakeRegistryStorage is // slither-disable-next-line shadowing-state /// @dev Reserves storage slots for future upgrades // solhint-disable-next-line - uint256[42] private __gap; + uint256[40] private __gap; } diff --git a/solidity/contracts/interfaces/avs/vendored/IECDSAStakeRegistryEventsAndErrors.sol b/solidity/contracts/interfaces/avs/vendored/IECDSAStakeRegistryEventsAndErrors.sol index 021d34db4..07f6323da 100644 --- a/solidity/contracts/interfaces/avs/vendored/IECDSAStakeRegistryEventsAndErrors.sol +++ b/solidity/contracts/interfaces/avs/vendored/IECDSAStakeRegistryEventsAndErrors.sol @@ -12,8 +12,6 @@ struct Quorum { StrategyParams[] strategies; // An array of strategy parameters to define the quorum } -/// part of mock interfaces for vendoring necessary Eigenlayer contracts for the hyperlane AVS -/// @author Layr Labs, Inc. interface IECDSAStakeRegistryEventsAndErrors { /// @notice Emitted when the system registers an operator /// @param _operator The address of the registered operator @@ -61,7 +59,19 @@ interface IECDSAStakeRegistryEventsAndErrors { /// @notice Emits when setting a new threshold weight. event ThresholdWeightUpdated(uint256 _thresholdWeight); + /// @notice Emitted when an operator's signing key is updated + /// @param operator The address of the operator whose signing key was updated + /// @param updateBlock The block number at which the signing key was updated + /// @param newSigningKey The operator's signing key after the update + /// @param oldSigningKey The operator's signing key before the update + event SigningKeyUpdate( + address indexed operator, + uint256 indexed updateBlock, + address indexed newSigningKey, + address oldSigningKey + ); /// @notice Indicates when the lengths of the signers array and signatures array do not match. + error LengthMismatch(); /// @notice Indicates encountering an invalid length for the signers or signatures array. @@ -76,6 +86,9 @@ interface IECDSAStakeRegistryEventsAndErrors { /// @notice Thrown when missing operators in an update error MustUpdateAllOperators(); + /// @notice Reference blocks must be for blocks that have already been confirmed + error InvalidReferenceBlock(); + /// @notice Indicates operator weights were out of sync and the signed weight exceed the total error InvalidSignedWeight(); diff --git a/solidity/contracts/test/ERC20Test.sol b/solidity/contracts/test/ERC20Test.sol index 8d4580c24..03b306429 100644 --- a/solidity/contracts/test/ERC20Test.sol +++ b/solidity/contracts/test/ERC20Test.sol @@ -3,6 +3,7 @@ pragma solidity >=0.8.0; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "../token/interfaces/IXERC20Lockbox.sol"; import "../token/interfaces/IXERC20.sol"; import "../token/interfaces/IFiatToken.sol"; @@ -66,15 +67,50 @@ contract XERC20Test is ERC20Test, IXERC20 { _burn(account, amount); } - function setLimits( - address _bridge, - uint256 _mintingLimit, - uint256 _burningLimit - ) external { - require(false); + function setLimits(address, uint256, uint256) external pure { + assert(false); } - function owner() external returns (address) { + function owner() external pure returns (address) { return address(0x0); } } + +contract XERC20LockboxTest is IXERC20Lockbox { + IXERC20 public immutable XERC20; + IERC20 public immutable ERC20; + + constructor( + string memory name, + string memory symbol, + uint256 totalSupply, + uint8 __decimals + ) { + ERC20Test erc20 = new ERC20Test(name, symbol, totalSupply, __decimals); + erc20.transfer(msg.sender, totalSupply); + ERC20 = erc20; + XERC20 = new XERC20Test(name, symbol, 0, __decimals); + } + + function depositTo(address _user, uint256 _amount) public { + ERC20.transferFrom(msg.sender, address(this), _amount); + XERC20.mint(_user, _amount); + } + + function deposit(uint256 _amount) external { + depositTo(msg.sender, _amount); + } + + function depositNativeTo(address) external payable { + assert(false); + } + + function withdrawTo(address _user, uint256 _amount) public { + XERC20.burn(msg.sender, _amount); + ERC20Test(address(ERC20)).mintTo(_user, _amount); + } + + function withdraw(uint256 _amount) external { + withdrawTo(msg.sender, _amount); + } +} diff --git a/solidity/contracts/token/README.md b/solidity/contracts/token/README.md index 3d8c90082..99edbd11d 100644 --- a/solidity/contracts/token/README.md +++ b/solidity/contracts/token/README.md @@ -6,7 +6,7 @@ For instructions on deploying Warp Routes, see [the deployment documentation](ht ## Warp Route Architecture -A Warp Route is a collection of [`TokenRouter`](./contracts/libs/TokenRouter.sol) contracts deployed across a set of Hyperlane chains. These contracts leverage the `Router` pattern to implement access control and routing logic for remote token transfers. These contracts send and receive [`Messages`](./contracts/libs/Message.sol) which encode payloads containing a transfer `amount` and `recipient` address. +A Warp Route is a collection of [`TokenRouter`](./libs/TokenRouter.sol) contracts deployed across a set of Hyperlane chains. These contracts leverage the `Router` pattern to implement access control and routing logic for remote token transfers. These contracts send and receive [`Messages`](./libs/TokenMessage.sol) which encode payloads containing a transfer `amount` and `recipient` address. ```mermaid %%{ init: { @@ -39,7 +39,7 @@ graph LR Mailbox_G[(Mailbox)] end - HYP_E -. "router" .- HYP_P -. "router" .- HYP_G + HYP_E -. "TokenMessage" .- HYP_P -. "TokenMessage" .- HYP_G ``` diff --git a/solidity/contracts/token/extensions/HypXERC20Lockbox.sol b/solidity/contracts/token/extensions/HypXERC20Lockbox.sol index f4a860917..6c95abd3e 100644 --- a/solidity/contracts/token/extensions/HypXERC20Lockbox.sol +++ b/solidity/contracts/token/extensions/HypXERC20Lockbox.sol @@ -17,18 +17,39 @@ contract HypXERC20Lockbox is HypERC20Collateral { ) HypERC20Collateral(address(IXERC20Lockbox(_lockbox).ERC20()), _mailbox) { lockbox = IXERC20Lockbox(_lockbox); xERC20 = lockbox.XERC20(); + approveLockbox(); + } - // grant infinite approvals to lockbox + /** + * @notice Approve the lockbox to spend the wrapped token and xERC20 + * @dev This function is idempotent and need not be access controlled + */ + function approveLockbox() public { require( - IERC20(wrappedToken).approve(_lockbox, MAX_INT), + IERC20(wrappedToken).approve(address(lockbox), MAX_INT), "erc20 lockbox approve failed" ); require( - xERC20.approve(_lockbox, MAX_INT), + xERC20.approve(address(lockbox), MAX_INT), "xerc20 lockbox approve failed" ); } + /** + * @notice Initialize the contract + * @param _hook The address of the hook contract + * @param _ism The address of the interchain security module + * @param _owner The address of the owner + */ + function initialize( + address _hook, + address _ism, + address _owner + ) public override initializer { + approveLockbox(); + _MailboxClient_initialize(_hook, _ism, _owner); + } + function _transferFromSender( uint256 _amount ) internal override returns (bytes memory) { diff --git a/solidity/coverage.sh b/solidity/coverage.sh index bd9a3e232..a3f7d463f 100755 --- a/solidity/coverage.sh +++ b/solidity/coverage.sh @@ -14,7 +14,7 @@ fi lcov --version # exclude FastTokenRouter until https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/2806 -EXCLUDE="*test* *mock* *node_modules* *FastHyp*" +EXCLUDE="*test* *mock* *node_modules* *script* *FastHyp*" lcov \ --rc lcov_branch_coverage=1 \ --remove lcov.info $EXCLUDE \ diff --git a/solidity/foundry.toml b/solidity/foundry.toml index 8180d9b58..51a9912cb 100644 --- a/solidity/foundry.toml +++ b/solidity/foundry.toml @@ -14,7 +14,11 @@ fs_permissions = [ { access = "read", path = "./script/avs/"}, { access = "write", path = "./fixtures" } ] -ignored_warnings_from = ['fx-portal'] +ignored_warnings_from = [ + 'lib', + 'test', + 'contracts/test' +] [profile.ci] verbosity = 4 diff --git a/solidity/lib/forge-std b/solidity/lib/forge-std index e8a047e3f..52715a217 160000 --- a/solidity/lib/forge-std +++ b/solidity/lib/forge-std @@ -1 +1 @@ -Subproject commit e8a047e3f40f13fa37af6fe14e6e06283d9a060e +Subproject commit 52715a217dc51d0de15877878ab8213f6cbbbab5 diff --git a/solidity/package.json b/solidity/package.json index 3115dab14..bdbe1f659 100644 --- a/solidity/package.json +++ b/solidity/package.json @@ -1,10 +1,10 @@ { "name": "@hyperlane-xyz/core", "description": "Core solidity contracts for Hyperlane", - "version": "3.12.2", + "version": "3.13.0", "dependencies": { "@eth-optimism/contracts": "^0.6.0", - "@hyperlane-xyz/utils": "3.12.2", + "@hyperlane-xyz/utils": "3.13.0", "@layerzerolabs/lz-evm-oapp-v2": "2.0.2", "@openzeppelin/contracts": "^4.9.3", "@openzeppelin/contracts-upgradeable": "^v4.9.3", diff --git a/solidity/script/avs/DeployAVS.s.sol b/solidity/script/avs/DeployAVS.s.sol index f2fce2d1d..b38762be6 100644 --- a/solidity/script/avs/DeployAVS.s.sol +++ b/solidity/script/avs/DeployAVS.s.sol @@ -12,6 +12,7 @@ import {ProxyAdmin} from "../../contracts/upgrade/ProxyAdmin.sol"; import {TransparentUpgradeableProxy} from "../../contracts/upgrade/TransparentUpgradeableProxy.sol"; import {ECDSAStakeRegistry} from "../../contracts/avs/ECDSAStakeRegistry.sol"; import {Quorum, StrategyParams} from "../../contracts/interfaces/avs/vendored/IECDSAStakeRegistryEventsAndErrors.sol"; +import {ECDSAServiceManagerBase} from "../../contracts/avs/ECDSAServiceManagerBase.sol"; import {HyperlaneServiceManager} from "../../contracts/avs/HyperlaneServiceManager.sol"; import {TestPaymentCoordinator} from "../../contracts/test/avs/TestPaymentCoordinator.sol"; @@ -42,6 +43,11 @@ contract DeployAVS is Script { ); string memory json = vm.readFile(path); + proxyAdmin = ProxyAdmin( + json.readAddress( + string(abi.encodePacked(".", targetEnv, ".proxyAdmin")) + ) + ); avsDirectory = IAVSDirectory( json.readAddress( string(abi.encodePacked(".", targetEnv, ".avsDirectory")) @@ -88,15 +94,14 @@ contract DeployAVS is Script { } } - function run(string memory network) external { + function run(string memory network, string memory metadataUri) external { deployerPrivateKey = vm.envUint("DEPLOYER_PRIVATE_KEY"); + address deployerAddress = vm.addr(deployerPrivateKey); _loadEigenlayerAddresses(network); vm.startBroadcast(deployerPrivateKey); - proxyAdmin = new ProxyAdmin(); - ECDSAStakeRegistry stakeRegistryImpl = new ECDSAStakeRegistry( delegationManager ); @@ -118,7 +123,7 @@ contract DeployAVS is Script { address(proxyAdmin), abi.encodeWithSelector( HyperlaneServiceManager.initialize.selector, - msg.sender + address(deployerAddress) ) ); @@ -131,7 +136,24 @@ contract DeployAVS is Script { quorum ) ); + + HyperlaneServiceManager hsm = HyperlaneServiceManager( + address(hsmProxy) + ); + require(success, "Failed to initialize ECDSAStakeRegistry"); + require( + ECDSAStakeRegistry(address(stakeRegistryProxy)).owner() == + address(deployerAddress), + "Owner of ECDSAStakeRegistry is not the deployer" + ); + require( + HyperlaneServiceManager(address(hsmProxy)).owner() == + address(deployerAddress), + "Owner of HyperlaneServiceManager is not the deployer" + ); + + hsm.updateAVSMetadataURI(metadataUri); console.log( "ECDSAStakeRegistry Implementation: ", diff --git a/solidity/script/avs/eigenlayer_addresses.json b/solidity/script/avs/eigenlayer_addresses.json index d8890a77b..60e2fceea 100644 --- a/solidity/script/avs/eigenlayer_addresses.json +++ b/solidity/script/avs/eigenlayer_addresses.json @@ -1,5 +1,6 @@ { "ethereum": { + "proxyAdmin": "0x75EE15Ee1B4A75Fa3e2fDF5DF3253c25599cc659", "delegationManager": "0x39053D51B77DC0d36036Fc1fCc8Cb819df8Ef37A", "avsDirectory": "0x135DDa560e946695d6f155dACaFC6f1F25C1F5AF", "paymentCoordinator": "", @@ -19,6 +20,7 @@ ] }, "holesky": { + "proxyAdmin": "0x33dB966328Ea213b0f76eF96CA368AB37779F065", "delegationManager": "0xA44151489861Fe9e3055d95adC98FbD462B948e7", "avsDirectory": "0x055733000064333CaDDbC92763c58BF0192fFeBf", "paymentCoordinator": "", diff --git a/solidity/script/xerc20/.env.blast b/solidity/script/xerc20/.env.blast new file mode 100644 index 000000000..b0db0d828 --- /dev/null +++ b/solidity/script/xerc20/.env.blast @@ -0,0 +1,4 @@ +export ROUTER_ADDRESS=0xA34ceDf9068C5deE726C67A4e1DCfCc2D6E2A7fD +export ERC20_ADDRESS=0x2416092f143378750bb29b79eD961ab195CcEea5 +export XERC20_ADDRESS=0x2416092f143378750bb29b79eD961ab195CcEea5 +export RPC_URL="https://rpc.blast.io" diff --git a/solidity/script/xerc20/.env.ethereum b/solidity/script/xerc20/.env.ethereum new file mode 100644 index 000000000..4d6366a8c --- /dev/null +++ b/solidity/script/xerc20/.env.ethereum @@ -0,0 +1,5 @@ +export ROUTER_ADDRESS=0x8dfbEA2582F41c8C4Eb25252BbA392fd3c09449A +export ADMIN_ADDRESS=0xa5B0D537CeBE97f087Dc5FE5732d70719caaEc1D +export ERC20_ADDRESS=0xbf5495Efe5DB9ce00f80364C8B423567e58d2110 +export XERC20_ADDRESS=0x2416092f143378750bb29b79eD961ab195CcEea5 +export RPC_URL="https://eth.merkle.io" diff --git a/solidity/script/xerc20/ApproveLockbox.s.sol b/solidity/script/xerc20/ApproveLockbox.s.sol new file mode 100644 index 000000000..182306eab --- /dev/null +++ b/solidity/script/xerc20/ApproveLockbox.s.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.8.0; + +import "forge-std/Script.sol"; + +import {AnvilRPC} from "test/AnvilRPC.sol"; +import {TypeCasts} from "contracts/libs/TypeCasts.sol"; + +import {ITransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; + +import {ProxyAdmin} from "contracts/upgrade/ProxyAdmin.sol"; + +import {HypXERC20Lockbox} from "contracts/token/extensions/HypXERC20Lockbox.sol"; +import {IXERC20Lockbox} from "contracts/token/interfaces/IXERC20Lockbox.sol"; +import {IXERC20} from "contracts/token/interfaces/IXERC20.sol"; +import {IERC20} from "contracts/token/interfaces/IXERC20.sol"; + +// source .env. +// forge script ApproveLockbox.s.sol --broadcast --rpc-url localhost:XXXX +contract ApproveLockbox is Script { + address router = vm.envAddress("ROUTER_ADDRESS"); + address admin = vm.envAddress("ADMIN_ADDRESS"); + uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + + ITransparentUpgradeableProxy proxy = ITransparentUpgradeableProxy(router); + HypXERC20Lockbox old = HypXERC20Lockbox(router); + address lockbox = address(old.lockbox()); + address mailbox = address(old.mailbox()); + ProxyAdmin proxyAdmin = ProxyAdmin(admin); + + function run() external { + assert(proxyAdmin.getProxyAdmin(proxy) == admin); + + vm.startBroadcast(deployerPrivateKey); + HypXERC20Lockbox logic = new HypXERC20Lockbox(lockbox, mailbox); + proxyAdmin.upgradeAndCall( + proxy, + address(logic), + abi.encodeCall(HypXERC20Lockbox.approveLockbox, ()) + ); + vm.stopBroadcast(); + + vm.expectRevert("Initializable: contract is already initialized"); + HypXERC20Lockbox(address(proxy)).initialize( + address(0), + address(0), + mailbox + ); + } +} diff --git a/solidity/script/xerc20/GrantLimits.s.sol b/solidity/script/xerc20/GrantLimits.s.sol new file mode 100644 index 000000000..e2c79bae6 --- /dev/null +++ b/solidity/script/xerc20/GrantLimits.s.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.8.0; + +import "forge-std/Script.sol"; + +import {AnvilRPC} from "test/AnvilRPC.sol"; + +import {IXERC20Lockbox} from "contracts/token/interfaces/IXERC20Lockbox.sol"; +import {IXERC20} from "contracts/token/interfaces/IXERC20.sol"; +import {IERC20} from "contracts/token/interfaces/IXERC20.sol"; + +// source .env. +// anvil --fork-url $RPC_URL --port XXXX +// forge script GrantLimits.s.sol --broadcast --unlocked --rpc-url localhost:XXXX +contract GrantLimits is Script { + address tester = 0xa7ECcdb9Be08178f896c26b7BbD8C3D4E844d9Ba; + uint256 amount = 1 gwei; + + address router = vm.envAddress("ROUTER_ADDRESS"); + IERC20 erc20 = IERC20(vm.envAddress("ERC20_ADDRESS")); + IXERC20 xerc20 = IXERC20(vm.envAddress("XERC20_ADDRESS")); + + function runFrom(address account) internal { + AnvilRPC.setBalance(account, 1 ether); + AnvilRPC.impersonateAccount(account); + vm.broadcast(account); + } + + function run() external { + address owner = xerc20.owner(); + runFrom(owner); + xerc20.setLimits(router, amount, amount); + + runFrom(address(erc20)); + erc20.transfer(tester, amount); + } +} diff --git a/solidity/script/xerc20/ezETH.s.sol b/solidity/script/xerc20/ezETH.s.sol new file mode 100644 index 000000000..f6171eb63 --- /dev/null +++ b/solidity/script/xerc20/ezETH.s.sol @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.8.0; + +import "forge-std/Script.sol"; + +import {IXERC20Lockbox} from "../../contracts/token/interfaces/IXERC20Lockbox.sol"; +import {IXERC20} from "../../contracts/token/interfaces/IXERC20.sol"; +import {IERC20} from "../../contracts/token/interfaces/IXERC20.sol"; +import {HypXERC20Lockbox} from "../../contracts/token/extensions/HypXERC20Lockbox.sol"; +import {HypERC20Collateral} from "../../contracts/token/HypERC20Collateral.sol"; +import {HypXERC20} from "../../contracts/token/extensions/HypXERC20.sol"; +import {TransparentUpgradeableProxy} from "../../contracts/upgrade/TransparentUpgradeableProxy.sol"; +import {ProxyAdmin} from "../../contracts/upgrade/ProxyAdmin.sol"; + +import {TypeCasts} from "../../contracts/libs/TypeCasts.sol"; +import {TokenMessage} from "../../contracts/token/libs/TokenMessage.sol"; + +contract ezETH is Script { + using TypeCasts for address; + + string ETHEREUM_RPC_URL = vm.envString("ETHEREUM_RPC_URL"); + string BLAST_RPC_URL = vm.envString("BLAST_RPC_URL"); + + uint256 ethereumFork; + uint32 ethereumDomainId = 1; + address ethereumMailbox = 0xc005dc82818d67AF737725bD4bf75435d065D239; + address ethereumLockbox = 0xC8140dA31E6bCa19b287cC35531c2212763C2059; + + uint256 blastFork; + uint32 blastDomainId = 81457; + address blastXERC20 = 0x2416092f143378750bb29b79eD961ab195CcEea5; + address blastMailbox = 0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7; + + uint256 amount = 100; + + function setUp() public { + ethereumFork = vm.createFork(ETHEREUM_RPC_URL); + blastFork = vm.createFork(BLAST_RPC_URL); + } + + function run() external { + address deployer = address(this); + bytes32 recipient = deployer.addressToBytes32(); + bytes memory tokenMessage = TokenMessage.format(recipient, amount, ""); + vm.selectFork(ethereumFork); + HypXERC20Lockbox hypXERC20Lockbox = new HypXERC20Lockbox( + ethereumLockbox, + ethereumMailbox + ); + ProxyAdmin ethAdmin = new ProxyAdmin(); + TransparentUpgradeableProxy ethProxy = new TransparentUpgradeableProxy( + address(hypXERC20Lockbox), + address(ethAdmin), + abi.encodeCall( + HypXERC20Lockbox.initialize, + (address(0), address(0), deployer) + ) + ); + hypXERC20Lockbox = HypXERC20Lockbox(address(ethProxy)); + + vm.selectFork(blastFork); + HypXERC20 hypXERC20 = new HypXERC20(blastXERC20, blastMailbox); + ProxyAdmin blastAdmin = new ProxyAdmin(); + TransparentUpgradeableProxy blastProxy = new TransparentUpgradeableProxy( + address(hypXERC20), + address(blastAdmin), + abi.encodeCall( + HypERC20Collateral.initialize, + (address(0), address(0), deployer) + ) + ); + hypXERC20 = HypXERC20(address(blastProxy)); + hypXERC20.enrollRemoteRouter( + ethereumDomainId, + address(hypXERC20Lockbox).addressToBytes32() + ); + + // grant `amount` mint and burn limit to warp route + vm.prank(IXERC20(blastXERC20).owner()); + IXERC20(blastXERC20).setLimits(address(hypXERC20), amount, amount); + + // test sending `amount` on warp route + vm.prank(0x7BE481D464CAD7ad99500CE8A637599eB8d0FCDB); // ezETH whale + IXERC20(blastXERC20).transfer(address(this), amount); + IXERC20(blastXERC20).approve(address(hypXERC20), amount); + uint256 value = hypXERC20.quoteGasPayment(ethereumDomainId); + hypXERC20.transferRemote{value: value}( + ethereumDomainId, + recipient, + amount + ); + + // test receiving `amount` on warp route + vm.prank(blastMailbox); + hypXERC20.handle( + ethereumDomainId, + address(hypXERC20Lockbox).addressToBytes32(), + tokenMessage + ); + + vm.selectFork(ethereumFork); + hypXERC20Lockbox.enrollRemoteRouter( + blastDomainId, + address(hypXERC20).addressToBytes32() + ); + + // grant `amount` mint and burn limit to warp route + IXERC20 ethereumXERC20 = hypXERC20Lockbox.xERC20(); + vm.prank(ethereumXERC20.owner()); + ethereumXERC20.setLimits(address(hypXERC20Lockbox), amount, amount); + + // test sending `amount` on warp route + IERC20 erc20 = IXERC20Lockbox(ethereumLockbox).ERC20(); + vm.prank(ethereumLockbox); + erc20.transfer(address(this), amount); + erc20.approve(address(hypXERC20Lockbox), amount); + hypXERC20Lockbox.transferRemote(blastDomainId, recipient, amount); + + // test receiving `amount` on warp route + vm.prank(ethereumMailbox); + hypXERC20Lockbox.handle( + blastDomainId, + address(hypXERC20).addressToBytes32(), + tokenMessage + ); + } +} diff --git a/solidity/test/AnvilRPC.sol b/solidity/test/AnvilRPC.sol new file mode 100644 index 000000000..eb1be413a --- /dev/null +++ b/solidity/test/AnvilRPC.sol @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.8.0; + +import "forge-std/Vm.sol"; +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; + +// see https://book.getfoundry.sh/reference/anvil/#supported-rpc-methods +library AnvilRPC { + using Strings for address; + using Strings for uint256; + + using AnvilRPC for string; + using AnvilRPC for string[1]; + using AnvilRPC for string[2]; + using AnvilRPC for string[3]; + + Vm private constant vm = + Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); + + string private constant OPEN_ARRAY = "["; + string private constant CLOSE_ARRAY = "]"; + string private constant COMMA = ","; + string private constant EMPTY_ARRAY = "[]"; + + function escaped( + string memory value + ) internal pure returns (string memory) { + return string.concat(ESCAPED_QUOTE, value, ESCAPED_QUOTE); + } + + function toString( + string[1] memory values + ) internal pure returns (string memory) { + return string.concat(OPEN_ARRAY, values[0], CLOSE_ARRAY); + } + + function toString( + string[2] memory values + ) internal pure returns (string memory) { + return + string.concat(OPEN_ARRAY, values[0], COMMA, values[1], CLOSE_ARRAY); + } + + function toString( + string[3] memory values + ) internal pure returns (string memory) { + return + string.concat( + OPEN_ARRAY, + values[0], + COMMA, + values[1], + COMMA, + values[2], + CLOSE_ARRAY + ); + } + + function impersonateAccount(address account) internal { + vm.rpc( + "anvil_impersonateAccount", + [account.toHexString().escaped()].toString() + ); + } + + function setBalance(address account, uint256 balance) internal { + vm.rpc( + "anvil_setBalance", + [account.toHexString().escaped(), balance.toString()].toString() + ); + } + + function setCode(address account, bytes memory code) internal { + vm.rpc( + "anvil_setCode", + [account.toHexString().escaped(), string(code).escaped()].toString() + ); + } + + function setStorageAt( + address account, + uint256 slot, + uint256 value + ) internal { + vm.rpc( + "anvil_setStorageAt", + [ + account.toHexString().escaped(), + slot.toHexString(), + value.toHexString() + ].toString() + ); + } + + function resetFork(string memory rpcUrl) internal { + string memory obj = string.concat( + // solhint-disable-next-line quotes + '{"forking":{"jsonRpcUrl":', + string(rpcUrl).escaped(), + "}}" + ); + vm.rpc("anvil_reset", [obj].toString()); + } +} + +// here to prevent syntax highlighting from breaking +string constant ESCAPED_QUOTE = '"'; diff --git a/solidity/test/avs/HyperlaneServiceManager.t.sol b/solidity/test/avs/HyperlaneServiceManager.t.sol index 4ea9ce8f1..80d49f974 100644 --- a/solidity/test/avs/HyperlaneServiceManager.t.sol +++ b/solidity/test/avs/HyperlaneServiceManager.t.sol @@ -29,6 +29,7 @@ contract HyperlaneServiceManagerTest is EigenlayerBase { // Operator info uint256 operatorPrivateKey = 0xdeadbeef; address operator; + address avsSigningKey = address(0xc0ffee); bytes32 emptySalt; uint256 maxExpiry = type(uint256).max; @@ -97,9 +98,11 @@ contract HyperlaneServiceManagerTest is EigenlayerBase { emptySalt, maxExpiry ); + + vm.prank(operator); _ecdsaStakeRegistry.registerOperatorWithSignature( - operator, - operatorSignature + operatorSignature, + avsSigningKey ); // assert @@ -122,12 +125,13 @@ contract HyperlaneServiceManagerTest is EigenlayerBase { maxExpiry ); + vm.prank(operator); vm.expectRevert( "EIP1271SignatureUtils.checkSignature_EIP1271: signature not from signer" ); _ecdsaStakeRegistry.registerOperatorWithSignature( - operator, - operatorSignature + operatorSignature, + avsSigningKey ); // assert @@ -409,9 +413,10 @@ contract HyperlaneServiceManagerTest is EigenlayerBase { maxExpiry ); + vm.prank(operator); _ecdsaStakeRegistry.registerOperatorWithSignature( - operator, - operatorSignature + operatorSignature, + avsSigningKey ); } diff --git a/solidity/test/token/HypERC20.t.sol b/solidity/test/token/HypERC20.t.sol index 82c5359b7..300e59c54 100644 --- a/solidity/test/token/HypERC20.t.sol +++ b/solidity/test/token/HypERC20.t.sol @@ -19,13 +19,14 @@ import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transpa import {Mailbox} from "../../contracts/Mailbox.sol"; import {TypeCasts} from "../../contracts/libs/TypeCasts.sol"; import {TestMailbox} from "../../contracts/test/TestMailbox.sol"; -import {XERC20Test, FiatTokenTest, ERC20Test} from "../../contracts/test/ERC20Test.sol"; +import {XERC20LockboxTest, XERC20Test, FiatTokenTest, ERC20Test} from "../../contracts/test/ERC20Test.sol"; import {TestPostDispatchHook} from "../../contracts/test/TestPostDispatchHook.sol"; import {TestInterchainGasPaymaster} from "../../contracts/test/TestInterchainGasPaymaster.sol"; import {GasRouter} from "../../contracts/client/GasRouter.sol"; import {HypERC20} from "../../contracts/token/HypERC20.sol"; import {HypERC20Collateral} from "../../contracts/token/HypERC20Collateral.sol"; +import {HypXERC20Lockbox} from "../../contracts/token/extensions/HypXERC20Lockbox.sol"; import {IXERC20} from "../../contracts/token/interfaces/IXERC20.sol"; import {IFiatToken} from "../../contracts/token/interfaces/IFiatToken.sol"; import {HypXERC20} from "../../contracts/token/extensions/HypXERC20.sol"; @@ -442,6 +443,80 @@ contract HypXERC20Test is HypTokenTest { } } +contract HypXERC20LockboxTest is HypTokenTest { + using TypeCasts for address; + HypXERC20Lockbox internal xerc20Lockbox; + + function setUp() public override { + super.setUp(); + + XERC20LockboxTest lockbox = new XERC20LockboxTest( + NAME, + SYMBOL, + TOTAL_SUPPLY, + DECIMALS + ); + primaryToken = ERC20Test(address(lockbox.ERC20())); + + localToken = new HypXERC20Lockbox( + address(lockbox), + address(localMailbox) + ); + xerc20Lockbox = HypXERC20Lockbox(address(localToken)); + + xerc20Lockbox.enrollRemoteRouter( + DESTINATION, + address(remoteToken).addressToBytes32() + ); + + primaryToken.transfer(ALICE, 1000e18); + + _enrollRemoteTokenRouter(); + } + + uint256 constant MAX_INT = 2 ** 256 - 1; + + function testApproval() public { + assertEq( + xerc20Lockbox.xERC20().allowance( + address(localToken), + address(xerc20Lockbox.lockbox()) + ), + MAX_INT + ); + assertEq( + xerc20Lockbox.wrappedToken().allowance( + address(localToken), + address(xerc20Lockbox.lockbox()) + ), + MAX_INT + ); + } + + function testRemoteTransfer() public { + uint256 balanceBefore = localToken.balanceOf(ALICE); + + vm.prank(ALICE); + primaryToken.approve(address(localToken), TRANSFER_AMT); + vm.expectCall( + address(xerc20Lockbox.xERC20()), + abi.encodeCall(IXERC20.burn, (address(localToken), TRANSFER_AMT)) + ); + _performRemoteTransferWithEmit(REQUIRED_VALUE, TRANSFER_AMT, 0); + assertEq(localToken.balanceOf(ALICE), balanceBefore - TRANSFER_AMT); + } + + function testHandle() public { + uint256 balanceBefore = localToken.balanceOf(ALICE); + vm.expectCall( + address(xerc20Lockbox.xERC20()), + abi.encodeCall(IXERC20.mint, (address(localToken), TRANSFER_AMT)) + ); + _handleLocalTransfer(TRANSFER_AMT); + assertEq(localToken.balanceOf(ALICE), balanceBefore + TRANSFER_AMT); + } +} + contract HypFiatTokenTest is HypTokenTest { using TypeCasts for address; HypFiatToken internal fiatToken; diff --git a/tools/grafana/easy-relayer-dashboard-external.json b/tools/grafana/easy-relayer-dashboard-external.json new file mode 100644 index 000000000..49efaaf2f --- /dev/null +++ b/tools/grafana/easy-relayer-dashboard-external.json @@ -0,0 +1,436 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 66, + "links": [], + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "grafanacloud-prom" + }, + "description": "There shouldn't be abrupt changes, especially for a specific pair", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 78, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 2, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "grafanacloud-prom" + }, + "editorMode": "code", + "expr": "sum by (origin,remote)(round(increase(hyperlane_messages_processed_count[5m])))", + "hide": false, + "interval": "", + "legendFormat": "{{hyperlane_deployment}}: {{origin}}->{{remote}}", + "range": true, + "refId": "A" + } + ], + "title": "Messages Processed", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "grafanacloud-prom" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 9 + }, + "id": 1, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "grafanacloud-prom" + }, + "editorMode": "code", + "expr": "sum by (remote, queue_name)(\n hyperlane_submitter_queue_length{queue_name=\"prepare_queue\"}\n)", + "interval": "", + "legendFormat": "{{hyperlane_deployment }} - {{remote}}", + "range": true, + "refId": "A" + } + ], + "title": "Prepare queues (all)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "grafanacloud-prom" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 18 + }, + "id": 10, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "grafanacloud-prom" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "sum by(remote, queue_name) (hyperlane_submitter_queue_length{queue_name=\"submit_queue\"})", + "fullMetaSearch": false, + "includeNullMetadata": true, + "interval": "", + "legendFormat": "{{remote}}", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Submit Queues", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "grafanacloud-prom" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 26 + }, + "id": 8, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "grafanacloud-prom" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "sum by(remote, queue_name) (avg_over_time(hyperlane_submitter_queue_length{queue_name=\"confirm_queue\"}[20m]))", + "fullMetaSearch": false, + "includeNullMetadata": true, + "interval": "", + "legendFormat": "{{remote}}", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Confirm Queues", + "type": "timeseries" + } + ], + "refresh": "1m", + "schemaVersion": 39, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-7d", + "to": "now" + }, + "timeRangeUpdatedDuringEditOrView": false, + "timepicker": {}, + "timezone": "browser", + "title": "Easy Dashboard (External Sharing Template)", + "uid": "afdf6ada6uzvgga", + "version": 5, + "weekStart": "" +} \ No newline at end of file diff --git a/typescript/ccip-server/CHANGELOG.md b/typescript/ccip-server/CHANGELOG.md index bcee12745..40e0e5390 100644 --- a/typescript/ccip-server/CHANGELOG.md +++ b/typescript/ccip-server/CHANGELOG.md @@ -1,5 +1,7 @@ # @hyperlane-xyz/ccip-server +## 3.13.0 + ## 3.12.0 ## 3.11.1 diff --git a/typescript/ccip-server/package.json b/typescript/ccip-server/package.json index f7a9829c8..cc07a17b3 100644 --- a/typescript/ccip-server/package.json +++ b/typescript/ccip-server/package.json @@ -1,6 +1,6 @@ { "name": "@hyperlane-xyz/ccip-server", - "version": "3.12.2", + "version": "3.13.0", "description": "CCIP server", "typings": "dist/index.d.ts", "typedocMain": "src/index.ts", diff --git a/typescript/cli/CHANGELOG.md b/typescript/cli/CHANGELOG.md index 8f42fe354..c62b58046 100644 --- a/typescript/cli/CHANGELOG.md +++ b/typescript/cli/CHANGELOG.md @@ -1,5 +1,23 @@ # @hyperlane-xyz/cli +## 3.13.0 + +### Minor Changes + +- b22a0f453: Add hyperlane validator address command to retrieve validator address from AWS +- 39ea7cdef: Implement multi collateral warp routes +- babe816f8: Support xERC20 and xERC20 Lockbox in SDK and CLI +- b440d98be: Added support for registering/deregistering from the Hyperlane AVS + +### Patch Changes + +- b6b26e2bb: fix: minor change was breaking in registry export +- Updated dependencies [39ea7cdef] +- Updated dependencies [babe816f8] +- Updated dependencies [0cf692e73] + - @hyperlane-xyz/sdk@3.13.0 + - @hyperlane-xyz/utils@3.13.0 + ## 3.12.0 ### Minor Changes diff --git a/typescript/cli/cli.ts b/typescript/cli/cli.ts index 39cc3669f..a8b9127f3 100644 --- a/typescript/cli/cli.ts +++ b/typescript/cli/cli.ts @@ -5,6 +5,7 @@ import yargs from 'yargs'; import type { LogFormat, LogLevel } from '@hyperlane-xyz/utils'; import './env.js'; +import { avsCommand } from './src/commands/avs.js'; import { chainsCommand } from './src/commands/chains.js'; import { configCommand } from './src/commands/config.js'; import { deployCommand } from './src/commands/deploy.js'; @@ -49,6 +50,7 @@ try { }, contextMiddleware, ]) + .command(avsCommand) .command(chainsCommand) .command(configCommand) .command(deployCommand) diff --git a/typescript/cli/package.json b/typescript/cli/package.json index 311f50585..645d3a48b 100644 --- a/typescript/cli/package.json +++ b/typescript/cli/package.json @@ -1,13 +1,13 @@ { "name": "@hyperlane-xyz/cli", - "version": "3.12.2", + "version": "3.13.0", "description": "A command-line utility for common Hyperlane operations", "dependencies": { "@aws-sdk/client-kms": "^3.577.0", "@aws-sdk/client-s3": "^3.577.0", "@hyperlane-xyz/registry": "1.3.0", - "@hyperlane-xyz/sdk": "3.12.2", - "@hyperlane-xyz/utils": "3.12.2", + "@hyperlane-xyz/sdk": "3.13.0", + "@hyperlane-xyz/utils": "3.13.0", "@inquirer/prompts": "^3.0.0", "asn1.js": "^5.4.1", "bignumber.js": "^9.1.1", diff --git a/typescript/cli/src/avs/config.ts b/typescript/cli/src/avs/config.ts new file mode 100644 index 000000000..681ed9dee --- /dev/null +++ b/typescript/cli/src/avs/config.ts @@ -0,0 +1,19 @@ +import { ChainMap } from '@hyperlane-xyz/sdk'; +import { Address } from '@hyperlane-xyz/utils'; + +interface AVSContracts { + avsDirectory: Address; + proxyAdmin: Address; + ecdsaStakeRegistry: Address; + hyperlaneServiceManager: Address; +} + +// TODO: move to registry +export const avsAddresses: ChainMap = { + holesky: { + avsDirectory: '0x055733000064333CaDDbC92763c58BF0192fFeBf', + proxyAdmin: '0x33dB966328Ea213b0f76eF96CA368AB37779F065', + ecdsaStakeRegistry: '0xFfa913705484C9BAea32Ffe9945BeA099A1DFF72', + hyperlaneServiceManager: '0xc76E477437065093D353b7d56c81ff54D167B0Ab', + }, +}; diff --git a/typescript/cli/src/avs/stakeRegistry.ts b/typescript/cli/src/avs/stakeRegistry.ts new file mode 100644 index 000000000..9d23bffaa --- /dev/null +++ b/typescript/cli/src/avs/stakeRegistry.ts @@ -0,0 +1,164 @@ +import { password } from '@inquirer/prompts'; +import { BigNumberish, Wallet, utils } from 'ethers'; + +import { + ECDSAStakeRegistry__factory, + TestAVSDirectory__factory, +} from '@hyperlane-xyz/core'; +import { ChainName } from '@hyperlane-xyz/sdk'; +import { Address } from '@hyperlane-xyz/utils'; + +import { WriteCommandContext } from '../context/types.js'; +import { log, logBlue } from '../logger.js'; +import { readFileAtPath, resolvePath } from '../utils/files.js'; + +import { avsAddresses } from './config.js'; + +export type SignatureWithSaltAndExpiryStruct = { + signature: utils.BytesLike; + salt: utils.BytesLike; + expiry: BigNumberish; +}; + +export async function registerOperatorWithSignature({ + context, + chain, + operatorKeyPath, + avsSigningKey, +}: { + context: WriteCommandContext; + chain: ChainName; + operatorKeyPath: string; + avsSigningKey: Address; +}) { + const { multiProvider } = context; + + const operatorAsSigner = await readOperatorFromEncryptedJson(operatorKeyPath); + + const provider = multiProvider.getProvider(chain); + const connectedSigner = operatorAsSigner.connect(provider); + + const stakeRegistryAddress = avsAddresses[chain].ecdsaStakeRegistry; + + const ecdsaStakeRegistry = ECDSAStakeRegistry__factory.connect( + stakeRegistryAddress, + connectedSigner, + ); + + const domainId = multiProvider.getDomainId(chain); + const avsDirectoryAddress = avsAddresses[chain].avsDirectory; + const operatorSignature = await getOperatorSignature( + domainId, + avsAddresses[chain].hyperlaneServiceManager, + avsDirectoryAddress, + operatorAsSigner, + connectedSigner, + ); + + // check if the operator is already registered + const operatorStatus = await ecdsaStakeRegistry.operatorRegistered( + operatorAsSigner.address, + ); + if (operatorStatus) { + logBlue( + `Operator ${operatorAsSigner.address} already registered to Hyperlane AVS`, + ); + return; + } + + log( + `Registering operator ${operatorAsSigner.address} attesting ${avsSigningKey} with signature on ${chain}...`, + ); + await multiProvider.handleTx( + chain, + ecdsaStakeRegistry.registerOperatorWithSignature( + operatorSignature, + avsSigningKey, + ), + ); + logBlue(`Operator ${operatorAsSigner.address} registered to Hyperlane AVS`); +} + +export async function deregisterOperator({ + context, + chain, + operatorKeyPath, +}: { + context: WriteCommandContext; + chain: ChainName; + operatorKeyPath: string; +}) { + const { multiProvider } = context; + + const operatorAsSigner = await readOperatorFromEncryptedJson(operatorKeyPath); + + const provider = multiProvider.getProvider(chain); + const connectedSigner = operatorAsSigner.connect(provider); + + const stakeRegistryAddress = avsAddresses[chain].ecdsaStakeRegistry; + + const ecdsaStakeRegistry = ECDSAStakeRegistry__factory.connect( + stakeRegistryAddress, + connectedSigner, + ); + + log(`Deregistering operator ${operatorAsSigner.address} on ${chain}...`); + await multiProvider.handleTx(chain, ecdsaStakeRegistry.deregisterOperator()); + logBlue( + `Operator ${operatorAsSigner.address} deregistered from Hyperlane AVS`, + ); +} + +async function readOperatorFromEncryptedJson( + operatorKeyPath: string, +): Promise { + const encryptedJson = readFileAtPath(resolvePath(operatorKeyPath)); + + const keyFilePassword = await password({ + mask: '*', + message: 'Enter the password for the operator key file: ', + }); + + return await Wallet.fromEncryptedJson(encryptedJson, keyFilePassword); +} + +async function getOperatorSignature( + domain: number, + serviceManager: Address, + avsDirectory: Address, + operator: Wallet, + signer: Wallet, +): Promise { + const avsDirectoryContract = TestAVSDirectory__factory.connect( + avsDirectory, + signer, + ); + + // random salt is ok, because we register the operator right after + const salt = utils.hexZeroPad(utils.randomBytes(32), 32); + // give a expiry timestamp 1 hour from now + const expiry = utils.hexZeroPad( + utils.hexlify(Math.floor(Date.now() / 1000) + 60 * 60), + 32, + ); + + const signingHash = + await avsDirectoryContract.calculateOperatorAVSRegistrationDigestHash( + operator.address, + serviceManager, + salt, + expiry, + ); + + // Eigenlayer's AVSDirectory expects the signature over raw signed hash instead of EIP-191 compatible toEthSignedMessageHash + // see https://github.com/Layr-Labs/eigenlayer-contracts/blob/ef2ea4a7459884f381057aa9bbcd29c7148cfb63/src/contracts/libraries/EIP1271SignatureUtils.sol#L22 + const signature = operator + ._signingKey() + .signDigest(utils.arrayify(signingHash)); + + return { + signature: utils.joinSignature(signature), + salt, + expiry, + }; +} diff --git a/typescript/cli/src/commands/avs.ts b/typescript/cli/src/commands/avs.ts new file mode 100644 index 000000000..04a51b6b6 --- /dev/null +++ b/typescript/cli/src/commands/avs.ts @@ -0,0 +1,84 @@ +import { CommandModule, Options } from 'yargs'; + +import { ChainName } from '@hyperlane-xyz/sdk'; +import { Address } from '@hyperlane-xyz/utils'; + +import { + deregisterOperator, + registerOperatorWithSignature, +} from '../avs/stakeRegistry.js'; +import { CommandModuleWithWriteContext } from '../context/types.js'; +import { log } from '../logger.js'; + +/** + * Parent command + */ +export const avsCommand: CommandModule = { + command: 'avs', + describe: 'Interact with the Hyperlane AVS', + builder: (yargs) => + yargs + .command(registerCommand) + .command(deregisterCommand) + .version(false) + .demandCommand(), + handler: () => log('Command required'), +}; + +/** + * Registration command + */ +export const registrationOptions: { [k: string]: Options } = { + chain: { + type: 'string', + description: 'Chain to interact with the AVS on', + demandOption: true, + choices: ['holesky', 'ethereum'], + }, + operatorKeyPath: { + type: 'string', + description: 'Path to the operator key file', + demandOption: true, + }, + avsSigningKey: { + type: 'string', + description: 'Address of the AVS signing key', + demandOption: true, + }, +}; + +const registerCommand: CommandModuleWithWriteContext<{ + chain: ChainName; + operatorKeyPath: string; + avsSigningKey: Address; +}> = { + command: 'register', + describe: 'Register operator with the AVS', + builder: registrationOptions, + handler: async ({ context, chain, operatorKeyPath, avsSigningKey }) => { + await registerOperatorWithSignature({ + context, + chain, + operatorKeyPath, + avsSigningKey, + }); + process.exit(0); + }, +}; + +const deregisterCommand: CommandModuleWithWriteContext<{ + chain: ChainName; + operatorKeyPath: string; +}> = { + command: 'deregister', + describe: 'Deregister yourself with the AVS', + builder: registrationOptions, + handler: async ({ context, chain, operatorKeyPath }) => { + await deregisterOperator({ + context, + chain, + operatorKeyPath, + }); + process.exit(0); + }, +}; diff --git a/typescript/cli/src/consts.ts b/typescript/cli/src/consts.ts index 9e05f2fcb..584ad97b9 100644 --- a/typescript/cli/src/consts.ts +++ b/typescript/cli/src/consts.ts @@ -1,3 +1,4 @@ export const MINIMUM_CORE_DEPLOY_GAS = (1e8).toString(); export const MINIMUM_WARP_DEPLOY_GAS = (1e7).toString(); export const MINIMUM_TEST_SEND_GAS = (3e5).toString(); +export const MINIMUM_AVS_GAS = (3e6).toString(); diff --git a/typescript/cli/src/utils/files.ts b/typescript/cli/src/utils/files.ts index 9734b3488..844eb4b79 100644 --- a/typescript/cli/src/utils/files.ts +++ b/typescript/cli/src/utils/files.ts @@ -1,6 +1,7 @@ import { input } from '@inquirer/prompts'; import select from '@inquirer/select'; import fs from 'fs'; +import os from 'os'; import path from 'path'; import { parse as yamlParse, stringify as yamlStringify } from 'yaml'; @@ -15,6 +16,14 @@ export type ArtifactsFile = { description: string; }; +export function resolvePath(filePath: string): string { + if (filePath.startsWith('~')) { + const homedir = os.homedir(); + return path.join(homedir, filePath.slice(1)); + } + return filePath; +} + export function isFile(filepath: string) { if (!filepath) return false; try { diff --git a/typescript/cli/src/version.ts b/typescript/cli/src/version.ts index f1447b455..9fb80ae08 100644 --- a/typescript/cli/src/version.ts +++ b/typescript/cli/src/version.ts @@ -1 +1 @@ -export const VERSION = '3.12.2'; +export const VERSION = '3.13.0'; diff --git a/typescript/helloworld/CHANGELOG.md b/typescript/helloworld/CHANGELOG.md index 979fb8285..f5e8404be 100644 --- a/typescript/helloworld/CHANGELOG.md +++ b/typescript/helloworld/CHANGELOG.md @@ -1,5 +1,17 @@ # @hyperlane-xyz/helloworld +## 3.13.0 + +### Patch Changes + +- b6b26e2bb: fix: minor change was breaking in registry export +- Updated dependencies [39ea7cdef] +- Updated dependencies [babe816f8] +- Updated dependencies [b440d98be] +- Updated dependencies [0cf692e73] + - @hyperlane-xyz/sdk@3.13.0 + - @hyperlane-xyz/core@3.13.0 + ## 3.12.0 ### Patch Changes diff --git a/typescript/helloworld/package.json b/typescript/helloworld/package.json index 64d3140aa..7d3f62b3d 100644 --- a/typescript/helloworld/package.json +++ b/typescript/helloworld/package.json @@ -1,11 +1,11 @@ { "name": "@hyperlane-xyz/helloworld", "description": "A basic skeleton of an Hyperlane app", - "version": "3.12.2", + "version": "3.13.0", "dependencies": { - "@hyperlane-xyz/core": "3.12.2", + "@hyperlane-xyz/core": "3.13.0", "@hyperlane-xyz/registry": "1.3.0", - "@hyperlane-xyz/sdk": "3.12.2", + "@hyperlane-xyz/sdk": "3.13.0", "@openzeppelin/contracts-upgradeable": "^4.9.3", "ethers": "^5.7.2" }, diff --git a/typescript/infra/CHANGELOG.md b/typescript/infra/CHANGELOG.md index 21b608413..0696269d8 100644 --- a/typescript/infra/CHANGELOG.md +++ b/typescript/infra/CHANGELOG.md @@ -1,5 +1,23 @@ # @hyperlane-xyz/infra +## 3.13.0 + +### Minor Changes + +- 39ea7cdef: Implement multi collateral warp routes +- 0cf692e73: Implement metadata builder fetching from message + +### Patch Changes + +- b6b26e2bb: fix: minor change was breaking in registry export +- Updated dependencies [b6b26e2bb] +- Updated dependencies [39ea7cdef] +- Updated dependencies [babe816f8] +- Updated dependencies [0cf692e73] + - @hyperlane-xyz/helloworld@3.13.0 + - @hyperlane-xyz/sdk@3.13.0 + - @hyperlane-xyz/utils@3.13.0 + ## 3.12.0 ### Patch Changes diff --git a/typescript/infra/config/environments/mainnet3/chains.ts b/typescript/infra/config/environments/mainnet3/chains.ts index 0e5d4144e..c511b72e6 100644 --- a/typescript/infra/config/environments/mainnet3/chains.ts +++ b/typescript/infra/config/environments/mainnet3/chains.ts @@ -1,30 +1,23 @@ import { ChainMap, ChainMetadata } from '@hyperlane-xyz/sdk'; -import { objKeys } from '@hyperlane-xyz/utils'; -import { getChainMetadatas } from '../../../src/config/chain.js'; -import { getChain } from '../../registry.js'; +import { isEthereumProtocolChain } from '../../../src/utils/utils.js'; import { supportedChainNames } from './supportedChainNames.js'; export const environment = 'mainnet3'; -const { - ethereumMetadatas: defaultEthereumMainnetConfigs, - nonEthereumMetadatas: nonEthereumMainnetConfigs, -} = getChainMetadatas(supportedChainNames); +export const ethereumChainNames = supportedChainNames.filter( + isEthereumProtocolChain, +); -export const ethereumMainnetConfigs: ChainMap = { - ...defaultEthereumMainnetConfigs, +export const chainMetadataOverrides: ChainMap> = { bsc: { - ...getChain('bsc'), transactionOverrides: { gasPrice: 3 * 10 ** 9, // 3 gwei }, }, polygon: { - ...getChain('polygon'), blocks: { - ...getChain('polygon').blocks, confirmations: 3, }, transactionOverrides: { @@ -35,9 +28,7 @@ export const ethereumMainnetConfigs: ChainMap = { }, }, ethereum: { - ...getChain('ethereum'), blocks: { - ...getChain('ethereum').blocks, confirmations: 3, }, transactionOverrides: { @@ -46,7 +37,6 @@ export const ethereumMainnetConfigs: ChainMap = { }, }, scroll: { - ...getChain('scroll'), transactionOverrides: { // Scroll doesn't use EIP 1559 and the gas price that's returned is sometimes // too low for the transaction to be included in a reasonable amount of time - @@ -55,17 +45,9 @@ export const ethereumMainnetConfigs: ChainMap = { }, }, moonbeam: { - ...getChain('moonbeam'), transactionOverrides: { maxFeePerGas: 350 * 10 ** 9, // 350 gwei maxPriorityFeePerGas: 50 * 10 ** 9, // 50 gwei }, }, }; - -export const mainnetConfigs: ChainMap = { - ...ethereumMainnetConfigs, - ...nonEthereumMainnetConfigs, -}; - -export const ethereumChainNames = objKeys(ethereumMainnetConfigs); diff --git a/typescript/infra/config/environments/mainnet3/funding.ts b/typescript/infra/config/environments/mainnet3/funding.ts index f9f15f0a8..25e01b03d 100644 --- a/typescript/infra/config/environments/mainnet3/funding.ts +++ b/typescript/infra/config/environments/mainnet3/funding.ts @@ -1,5 +1,3 @@ -import { RpcConsensusType } from '@hyperlane-xyz/sdk'; - import { KeyFunderConfig } from '../../../src/config/funding.js'; import { Role } from '../../../src/roles.js'; import { Contexts } from '../../contexts.js'; @@ -9,7 +7,7 @@ import { environment } from './chains.js'; export const keyFunderConfig: KeyFunderConfig = { docker: { repo: 'gcr.io/abacus-labs-dev/hyperlane-monorepo', - tag: 'b22a0f4-20240523-140812', + tag: '7720875-20240531-072251', }, // We're currently using the same deployer/key funder key as mainnet2. // To minimize nonce clobbering we offset the key funder cron @@ -23,7 +21,6 @@ export const keyFunderConfig: KeyFunderConfig = { [Contexts.Hyperlane]: [Role.Relayer, Role.Kathy], [Contexts.ReleaseCandidate]: [Role.Relayer, Role.Kathy], }, - connectionType: RpcConsensusType.Fallback, // desired balance config desiredBalancePerChain: { arbitrum: '0.5', diff --git a/typescript/infra/config/environments/mainnet3/helloworld.ts b/typescript/infra/config/environments/mainnet3/helloworld.ts index 4f15ba912..0df01f1d7 100644 --- a/typescript/infra/config/environments/mainnet3/helloworld.ts +++ b/typescript/infra/config/environments/mainnet3/helloworld.ts @@ -1,5 +1,3 @@ -import { RpcConsensusType } from '@hyperlane-xyz/sdk'; - import { HelloWorldConfig, HelloWorldKathyRunMode, @@ -26,7 +24,6 @@ export const hyperlane: HelloWorldConfig = { }, messageSendTimeout: 1000 * 60 * 8, // 8 min messageReceiptTimeout: 1000 * 60 * 20, // 20 min - connectionType: RpcConsensusType.Fallback, cyclesBetweenEthereumMessages: 1, // Skip 1 cycle of Ethereum, i.e. send/receive Ethereum messages every 5 days (not great since we still send like 12 in that cycle) }, }; @@ -46,7 +43,6 @@ export const releaseCandidate: HelloWorldConfig = { }, messageSendTimeout: 1000 * 60 * 8, // 8 min messageReceiptTimeout: 1000 * 60 * 20, // 20 min - connectionType: RpcConsensusType.Fallback, }, }; diff --git a/typescript/infra/config/environments/mainnet3/index.ts b/typescript/infra/config/environments/mainnet3/index.ts index a8bc14983..94c76e53a 100644 --- a/typescript/infra/config/environments/mainnet3/index.ts +++ b/typescript/infra/config/environments/mainnet3/index.ts @@ -1,16 +1,20 @@ -import { ChainMetadata, RpcConsensusType } from '@hyperlane-xyz/sdk'; -import { ProtocolType, objFilter } from '@hyperlane-xyz/utils'; +import { IRegistry } from '@hyperlane-xyz/registry'; import { getKeysForRole, + getMultiProtocolProvider, getMultiProviderForRole, } from '../../../scripts/agent-utils.js'; +import { getRegistryForEnvironment } from '../../../src/config/chain.js'; import { EnvironmentConfig } from '../../../src/config/environment.js'; import { Role } from '../../../src/roles.js'; import { Contexts } from '../../contexts.js'; import { agents } from './agent.js'; -import { environment as environmentName, mainnetConfigs } from './chains.js'; +import { + chainMetadataOverrides, + environment as environmentName, +} from './chains.js'; import { core } from './core.js'; import { keyFunderConfig } from './funding.js'; import { helloWorld } from './helloworld.js'; @@ -18,34 +22,38 @@ import { igp } from './igp.js'; import { infrastructure } from './infrastructure.js'; import { bridgeAdapterConfigs, relayerConfig } from './liquidityLayer.js'; import { owners } from './owners.js'; +import { supportedChainNames } from './supportedChainNames.js'; + +const getRegistry = async (useSecrets = true): Promise => + getRegistryForEnvironment( + environmentName, + supportedChainNames, + chainMetadataOverrides, + useSecrets, + ); export const environment: EnvironmentConfig = { environment: environmentName, - chainMetadataConfigs: mainnetConfigs, - getMultiProvider: ( + supportedChainNames, + getRegistry, + getMultiProtocolProvider: async () => + getMultiProtocolProvider(await getRegistry()), + getMultiProvider: async ( context: Contexts = Contexts.Hyperlane, role: Role = Role.Deployer, - connectionType?: RpcConsensusType, - ) => { - const config = objFilter( - mainnetConfigs, - (_, chainMetadata): chainMetadata is ChainMetadata => - chainMetadata.protocol === ProtocolType.Ethereum, - ); - - return getMultiProviderForRole( - config, + useSecrets?: boolean, + ) => + getMultiProviderForRole( environmentName, + await getRegistry(useSecrets), context, role, undefined, - connectionType, - ); - }, + ), getKeys: ( context: Contexts = Contexts.Hyperlane, role: Role = Role.Deployer, - ) => getKeysForRole(mainnetConfigs, environmentName, context, role), + ) => getKeysForRole(environmentName, supportedChainNames, context, role), agents, core, igp, diff --git a/typescript/infra/config/environments/mainnet3/liquidityLayer.ts b/typescript/infra/config/environments/mainnet3/liquidityLayer.ts index ac311f447..0f3a6d287 100644 --- a/typescript/infra/config/environments/mainnet3/liquidityLayer.ts +++ b/typescript/infra/config/environments/mainnet3/liquidityLayer.ts @@ -2,7 +2,6 @@ import { BridgeAdapterConfig, BridgeAdapterType, ChainMap, - RpcConsensusType, } from '@hyperlane-xyz/sdk'; import { LiquidityLayerRelayerConfig } from '../../../src/config/middleware.js'; @@ -50,5 +49,4 @@ export const relayerConfig: LiquidityLayerRelayerConfig = { namespace: environment, prometheusPushGateway: 'http://prometheus-prometheus-pushgateway.monitoring.svc.cluster.local:9091', - connectionType: RpcConsensusType.Single, }; diff --git a/typescript/infra/config/environments/test/index.ts b/typescript/infra/config/environments/test/index.ts index c21e411e5..cdde17fe3 100644 --- a/typescript/infra/config/environments/test/index.ts +++ b/typescript/infra/config/environments/test/index.ts @@ -5,6 +5,7 @@ import { MultiProvider, testChainMetadata } from '@hyperlane-xyz/sdk'; import { EnvironmentConfig } from '../../../src/config/environment.js'; import { agents } from './agent.js'; +import { testChainNames } from './chains.js'; import { core } from './core.js'; import { igp } from './igp.js'; import { infra } from './infra.js'; @@ -12,7 +13,13 @@ import { owners } from './owners.js'; export const environment: EnvironmentConfig = { environment: 'test', - chainMetadataConfigs: testChainMetadata, + supportedChainNames: testChainNames, + getRegistry: () => { + throw new Error('Not implemented'); + }, + getMultiProtocolProvider: () => { + throw new Error('Not implemented'); + }, agents, core, igp, diff --git a/typescript/infra/config/environments/testnet4/agent.ts b/typescript/infra/config/environments/testnet4/agent.ts index 62c162869..4ce2ab88d 100644 --- a/typescript/infra/config/environments/testnet4/agent.ts +++ b/typescript/infra/config/environments/testnet4/agent.ts @@ -36,6 +36,7 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig = { bsctestnet: true, eclipsetestnet: false, fuji: true, + holesky: true, plumetestnet: true, scrollsepolia: true, sepolia: true, @@ -46,6 +47,7 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig = { bsctestnet: true, eclipsetestnet: false, fuji: true, + holesky: true, plumetestnet: true, scrollsepolia: true, sepolia: true, @@ -57,6 +59,7 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig = { // Cannot scrape non-EVM chains eclipsetestnet: false, fuji: true, + holesky: true, plumetestnet: true, scrollsepolia: true, sepolia: true, @@ -124,7 +127,7 @@ const hyperlane: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: 'c9c5d37-20240510-014327', + tag: 'e09a360-20240520-090014', }, chains: validatorChainConfig(Contexts.Hyperlane), }, diff --git a/typescript/infra/config/environments/testnet4/aw-validators/hyperlane.json b/typescript/infra/config/environments/testnet4/aw-validators/hyperlane.json index 071d8e5cc..a78d0e953 100644 --- a/typescript/infra/config/environments/testnet4/aw-validators/hyperlane.json +++ b/typescript/infra/config/environments/testnet4/aw-validators/hyperlane.json @@ -20,6 +20,9 @@ "0x43e915573d9f1383cbf482049e4a012290759e7f" ] }, + "holesky": { + "validators": ["0x7ab28ad88bb45867137ea823af88e2cb02359c03"] + }, "plumetestnet": { "validators": [ "0xe765a214849f3ecdf00793b97d00422f2d408ea6", diff --git a/typescript/infra/config/environments/testnet4/chains.ts b/typescript/infra/config/environments/testnet4/chains.ts index 318d67e7b..caa3c1018 100644 --- a/typescript/infra/config/environments/testnet4/chains.ts +++ b/typescript/infra/config/environments/testnet4/chains.ts @@ -1,24 +1,19 @@ import { ChainMap, ChainMetadata } from '@hyperlane-xyz/sdk'; -import { objKeys } from '@hyperlane-xyz/utils'; -import { getChainMetadatas } from '../../../src/config/chain.js'; -import { getChain } from '../../registry.js'; +import { isEthereumProtocolChain } from '../../../src/utils/utils.js'; import { supportedChainNames } from './supportedChainNames.js'; export const environment = 'testnet4'; -const { ethereumMetadatas: defaultEthereumMainnetConfigs } = - getChainMetadatas(supportedChainNames); +export const ethereumChainNames = supportedChainNames.filter( + isEthereumProtocolChain, +); -export const testnetConfigs: ChainMap = { - ...defaultEthereumMainnetConfigs, +export const chainMetadataOverrides: ChainMap> = { bsctestnet: { - ...getChain('bsctestnet'), transactionOverrides: { gasPrice: 8 * 10 ** 9, // 8 gwei }, }, }; - -export const ethereumChainNames = objKeys(defaultEthereumMainnetConfigs); diff --git a/typescript/infra/config/environments/testnet4/core/verification.json b/typescript/infra/config/environments/testnet4/core/verification.json index 858d975ff..5abcc05a8 100644 --- a/typescript/infra/config/environments/testnet4/core/verification.json +++ b/typescript/infra/config/environments/testnet4/core/verification.json @@ -1841,6 +1841,296 @@ "name": "PausableHook" } ], + "holesky": [ + { + "address": "0x33dB966328Ea213b0f76eF96CA368AB37779F065", + "constructorArguments": "", + "isProxy": false, + "name": "ProxyAdmin" + }, + { + "address": "0xB08d78F439e55D02C398519eef61606A5926245F", + "constructorArguments": "0000000000000000000000000000000000000000000000000000000000004268", + "isProxy": false, + "name": "Mailbox" + }, + { + "address": "0x46f7C5D896bbeC89bE1B19e4485e59b4Be49e9Cc", + "constructorArguments": "000000000000000000000000b08d78f439e55d02c398519eef61606a5926245f00000000000000000000000033db966328ea213b0f76ef96ca368ab37779f06500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0x679Dc08cC3A4acFeea2f7CAFAa37561aE0b41Ce7", + "constructorArguments": "000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", + "isProxy": false, + "name": "PausableIsm" + }, + { + "address": "0x98AAE089CaD930C64a76dD2247a2aC5773a4B8cE", + "constructorArguments": "00000000000000000000000046f7c5d896bbec89be1b19e4485e59b4be49e9cc", + "isProxy": false, + "name": "MerkleTreeHook" + }, + { + "address": "0x07009DA2249c388aD0f416a235AfE90D784e1aAc", + "constructorArguments": "00000000000000000000000046f7c5d896bbec89be1b19e4485e59b4be49e9cc000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000098aae089cad930c64a76dd2247a2ac5773a4b8ce", + "isProxy": false, + "name": "FallbackRoutingHook" + }, + { + "address": "0xF7561c34f17A32D5620583A3397C304e7038a7F6", + "constructorArguments": "", + "isProxy": false, + "name": "PausableHook" + }, + { + "address": "0x2b2a158B4059C840c7aC67399B153bb567D06303", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" + }, + { + "address": "0x5CE550e14B82a9F32A0aaF9eFc4Fce548D8A0B3e", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" + }, + { + "address": "0x5CBf4e70448Ed46c2616b04e9ebc72D29FF0cfA9", + "constructorArguments": "0000000000000000000000005ce550e14b82a9f32a0aaf9efc4fce548d8a0b3e00000000000000000000000033db966328ea213b0f76ef96ca368ab37779f06500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0x2b2a158B4059C840c7aC67399B153bb567D06303", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" + }, + { + "address": "0x5CE550e14B82a9F32A0aaF9eFc4Fce548D8A0B3e", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" + }, + { + "address": "0x5CBf4e70448Ed46c2616b04e9ebc72D29FF0cfA9", + "constructorArguments": "0000000000000000000000005ce550e14b82a9f32a0aaf9efc4fce548d8a0b3e00000000000000000000000033db966328ea213b0f76ef96ca368ab37779f06500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0x2b2a158B4059C840c7aC67399B153bb567D06303", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" + }, + { + "address": "0x5CE550e14B82a9F32A0aaF9eFc4Fce548D8A0B3e", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" + }, + { + "address": "0x5CBf4e70448Ed46c2616b04e9ebc72D29FF0cfA9", + "constructorArguments": "0000000000000000000000005ce550e14b82a9f32a0aaf9efc4fce548d8a0b3e00000000000000000000000033db966328ea213b0f76ef96ca368ab37779f06500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0x2b2a158B4059C840c7aC67399B153bb567D06303", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" + }, + { + "address": "0x5CE550e14B82a9F32A0aaF9eFc4Fce548D8A0B3e", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" + }, + { + "address": "0x5CBf4e70448Ed46c2616b04e9ebc72D29FF0cfA9", + "constructorArguments": "0000000000000000000000005ce550e14b82a9f32a0aaf9efc4fce548d8a0b3e00000000000000000000000033db966328ea213b0f76ef96ca368ab37779f06500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0x2b2a158B4059C840c7aC67399B153bb567D06303", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" + }, + { + "address": "0x5CE550e14B82a9F32A0aaF9eFc4Fce548D8A0B3e", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" + }, + { + "address": "0x5CBf4e70448Ed46c2616b04e9ebc72D29FF0cfA9", + "constructorArguments": "0000000000000000000000005ce550e14b82a9f32a0aaf9efc4fce548d8a0b3e00000000000000000000000033db966328ea213b0f76ef96ca368ab37779f06500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0x2b2a158B4059C840c7aC67399B153bb567D06303", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" + }, + { + "address": "0x5CE550e14B82a9F32A0aaF9eFc4Fce548D8A0B3e", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" + }, + { + "address": "0x5CBf4e70448Ed46c2616b04e9ebc72D29FF0cfA9", + "constructorArguments": "0000000000000000000000005ce550e14b82a9f32a0aaf9efc4fce548d8a0b3e00000000000000000000000033db966328ea213b0f76ef96ca368ab37779f06500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0x98AAE089CaD930C64a76dD2247a2aC5773a4B8cE", + "constructorArguments": "00000000000000000000000046f7c5d896bbec89be1b19e4485e59b4be49e9cc", + "isProxy": false, + "name": "MerkleTreeHook" + }, + { + "address": "0x07009DA2249c388aD0f416a235AfE90D784e1aAc", + "constructorArguments": "00000000000000000000000046f7c5d896bbec89be1b19e4485e59b4be49e9cc000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000098aae089cad930c64a76dd2247a2ac5773a4b8ce", + "isProxy": false, + "name": "FallbackRoutingHook" + }, + { + "address": "0xF7561c34f17A32D5620583A3397C304e7038a7F6", + "constructorArguments": "", + "isProxy": false, + "name": "PausableHook" + }, + { + "address": "0x2b2a158B4059C840c7aC67399B153bb567D06303", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" + }, + { + "address": "0x5CE550e14B82a9F32A0aaF9eFc4Fce548D8A0B3e", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" + }, + { + "address": "0x5CBf4e70448Ed46c2616b04e9ebc72D29FF0cfA9", + "constructorArguments": "0000000000000000000000005ce550e14b82a9f32a0aaf9efc4fce548d8a0b3e00000000000000000000000033db966328ea213b0f76ef96ca368ab37779f06500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0x2b2a158B4059C840c7aC67399B153bb567D06303", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" + }, + { + "address": "0x5CE550e14B82a9F32A0aaF9eFc4Fce548D8A0B3e", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" + }, + { + "address": "0x5CBf4e70448Ed46c2616b04e9ebc72D29FF0cfA9", + "constructorArguments": "0000000000000000000000005ce550e14b82a9f32a0aaf9efc4fce548d8a0b3e00000000000000000000000033db966328ea213b0f76ef96ca368ab37779f06500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0x2b2a158B4059C840c7aC67399B153bb567D06303", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" + }, + { + "address": "0x5CE550e14B82a9F32A0aaF9eFc4Fce548D8A0B3e", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" + }, + { + "address": "0x5CBf4e70448Ed46c2616b04e9ebc72D29FF0cfA9", + "constructorArguments": "0000000000000000000000005ce550e14b82a9f32a0aaf9efc4fce548d8a0b3e00000000000000000000000033db966328ea213b0f76ef96ca368ab37779f06500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0x2b2a158B4059C840c7aC67399B153bb567D06303", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" + }, + { + "address": "0x5CE550e14B82a9F32A0aaF9eFc4Fce548D8A0B3e", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" + }, + { + "address": "0x5CBf4e70448Ed46c2616b04e9ebc72D29FF0cfA9", + "constructorArguments": "0000000000000000000000005ce550e14b82a9f32a0aaf9efc4fce548d8a0b3e00000000000000000000000033db966328ea213b0f76ef96ca368ab37779f06500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0x2b2a158B4059C840c7aC67399B153bb567D06303", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" + }, + { + "address": "0x5CE550e14B82a9F32A0aaF9eFc4Fce548D8A0B3e", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" + }, + { + "address": "0x5CBf4e70448Ed46c2616b04e9ebc72D29FF0cfA9", + "constructorArguments": "0000000000000000000000005ce550e14b82a9f32a0aaf9efc4fce548d8a0b3e00000000000000000000000033db966328ea213b0f76ef96ca368ab37779f06500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0x2b2a158B4059C840c7aC67399B153bb567D06303", + "constructorArguments": "", + "isProxy": false, + "name": "StorageGasOracle" + }, + { + "address": "0x5CE550e14B82a9F32A0aaF9eFc4Fce548D8A0B3e", + "constructorArguments": "", + "isProxy": false, + "name": "InterchainGasPaymaster" + }, + { + "address": "0x5CBf4e70448Ed46c2616b04e9ebc72D29FF0cfA9", + "constructorArguments": "0000000000000000000000005ce550e14b82a9f32a0aaf9efc4fce548d8a0b3e00000000000000000000000033db966328ea213b0f76ef96ca368ab37779f06500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "name": "TransparentUpgradeableProxy" + }, + { + "address": "0x6b1bb4ce664Bb4164AEB4d3D2E7DE7450DD8084C", + "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", + "isProxy": false, + "name": "ProtocolFee" + }, + { + "address": "0xAb9B273366D794B7F80B4378bc8Aaca75C6178E2", + "constructorArguments": "00000000000000000000000046f7c5d896bbec89be1b19e4485e59b4be49e9cc", + "isProxy": false, + "name": "ValidatorAnnounce" + } + ], "moonbasealpha": [ { "address": "0xb241991527F1C21adE14F55589E5940aC4852Fa0", diff --git a/typescript/infra/config/environments/testnet4/funding.ts b/typescript/infra/config/environments/testnet4/funding.ts index 42d9c75c9..04ec56c98 100644 --- a/typescript/infra/config/environments/testnet4/funding.ts +++ b/typescript/infra/config/environments/testnet4/funding.ts @@ -1,5 +1,3 @@ -import { RpcConsensusType } from '@hyperlane-xyz/sdk'; - import { KeyFunderConfig } from '../../../src/config/funding.js'; import { Role } from '../../../src/roles.js'; import { Contexts } from '../../contexts.js'; @@ -9,7 +7,7 @@ import { environment } from './chains.js'; export const keyFunderConfig: KeyFunderConfig = { docker: { repo: 'gcr.io/abacus-labs-dev/hyperlane-monorepo', - tag: 'b22a0f4-20240523-140812', + tag: 'efa9025-20240605-091304', }, // We're currently using the same deployer key as testnet2. // To minimize nonce clobbering we offset the key funder cron @@ -23,13 +21,14 @@ export const keyFunderConfig: KeyFunderConfig = { [Contexts.Hyperlane]: [Role.Relayer, Role.Kathy], [Contexts.ReleaseCandidate]: [Role.Relayer, Role.Kathy], }, - connectionType: RpcConsensusType.Fallback, // desired balance config desiredBalancePerChain: { alfajores: '5', bsctestnet: '5', fuji: '5', plumetestnet: '0.2', + holesky: '5', + // Funder boosts itself upto 5x balance on L2 before dispersing funds scrollsepolia: '1', sepolia: '5', }, diff --git a/typescript/infra/config/environments/testnet4/gas-oracle.ts b/typescript/infra/config/environments/testnet4/gas-oracle.ts index 18d94c2e4..f16da2ed3 100644 --- a/typescript/infra/config/environments/testnet4/gas-oracle.ts +++ b/typescript/infra/config/environments/testnet4/gas-oracle.ts @@ -19,6 +19,7 @@ import { ethereumChainNames } from './chains.js'; const gasPrices: ChainMap = { alfajores: ethers.utils.parseUnits('10', 'gwei'), fuji: ethers.utils.parseUnits('30', 'gwei'), + holesky: ethers.utils.parseUnits('10', 'gwei'), bsctestnet: ethers.utils.parseUnits('15', 'gwei'), sepolia: ethers.utils.parseUnits('5', 'gwei'), scrollsepolia: ethers.utils.parseUnits('0.5', 'gwei'), @@ -48,6 +49,7 @@ const chainTokenRarity: ChainMap = { alfajores: Rarity.Common, fuji: Rarity.Rare, bsctestnet: Rarity.Rare, + holesky: Rarity.Common, sepolia: Rarity.Mythic, scrollsepolia: Rarity.Rare, chiado: Rarity.Common, diff --git a/typescript/infra/config/environments/testnet4/helloworld.ts b/typescript/infra/config/environments/testnet4/helloworld.ts index e6f094c7d..dee5ab3f2 100644 --- a/typescript/infra/config/environments/testnet4/helloworld.ts +++ b/typescript/infra/config/environments/testnet4/helloworld.ts @@ -1,5 +1,3 @@ -import { RpcConsensusType } from '@hyperlane-xyz/sdk'; - import { HelloWorldConfig, HelloWorldKathyRunMode, @@ -15,7 +13,7 @@ export const hyperlaneHelloworld: HelloWorldConfig = { kathy: { docker: { repo: 'gcr.io/abacus-labs-dev/hyperlane-monorepo', - tag: 'b22a0f4-20240523-140812', + tag: 'efa9025-20240605-091304', }, chainsToSkip: [], runEnv: environment, @@ -26,7 +24,6 @@ export const hyperlaneHelloworld: HelloWorldConfig = { }, messageSendTimeout: 1000 * 60 * 10, // 10 min messageReceiptTimeout: 1000 * 60 * 20, // 20 min - connectionType: RpcConsensusType.Fallback, }, }; @@ -35,7 +32,7 @@ export const releaseCandidateHelloworld: HelloWorldConfig = { kathy: { docker: { repo: 'gcr.io/abacus-labs-dev/hyperlane-monorepo', - tag: 'b22a0f4-20240523-140812', + tag: 'efa9025-20240605-091304', }, chainsToSkip: [], runEnv: environment, @@ -45,7 +42,6 @@ export const releaseCandidateHelloworld: HelloWorldConfig = { }, messageSendTimeout: 1000 * 60 * 8, // 8 min messageReceiptTimeout: 1000 * 60 * 20, // 20 min - connectionType: RpcConsensusType.Fallback, }, }; diff --git a/typescript/infra/config/environments/testnet4/index.ts b/typescript/infra/config/environments/testnet4/index.ts index 9eaa66e1c..1d1204083 100644 --- a/typescript/infra/config/environments/testnet4/index.ts +++ b/typescript/infra/config/environments/testnet4/index.ts @@ -1,15 +1,21 @@ -import { RpcConsensusType } from '@hyperlane-xyz/sdk'; +import { IRegistry } from '@hyperlane-xyz/registry'; +import { objMerge } from '@hyperlane-xyz/utils'; import { getKeysForRole, + getMultiProtocolProvider, getMultiProviderForRole, } from '../../../scripts/agent-utils.js'; +import { getRegistryForEnvironment } from '../../../src/config/chain.js'; import { EnvironmentConfig } from '../../../src/config/environment.js'; import { Role } from '../../../src/roles.js'; import { Contexts } from '../../contexts.js'; import { agents } from './agent.js'; -import { environment as environmentName, testnetConfigs } from './chains.js'; +import { + chainMetadataOverrides, + environment as environmentName, +} from './chains.js'; import { core } from './core.js'; import { keyFunderConfig } from './funding.js'; import { helloWorld } from './helloworld.js'; @@ -18,27 +24,38 @@ import { infrastructure } from './infrastructure.js'; import { bridgeAdapterConfigs } from './liquidityLayer.js'; import { liquidityLayerRelayerConfig } from './middleware.js'; import { owners } from './owners.js'; +import { supportedChainNames } from './supportedChainNames.js'; + +const getRegistry = async (useSecrets = true): Promise => + getRegistryForEnvironment( + environmentName, + supportedChainNames, + chainMetadataOverrides, + useSecrets, + ); export const environment: EnvironmentConfig = { environment: environmentName, - chainMetadataConfigs: testnetConfigs, - getMultiProvider: ( + supportedChainNames, + getRegistry, + getMultiProtocolProvider: async () => + getMultiProtocolProvider(await getRegistry()), + getMultiProvider: async ( context: Contexts = Contexts.Hyperlane, role: Role = Role.Deployer, - connectionType?: RpcConsensusType, + useSecrets?: boolean, ) => getMultiProviderForRole( - testnetConfigs, environmentName, + await getRegistry(useSecrets), context, role, undefined, - connectionType, ), getKeys: ( context: Contexts = Contexts.Hyperlane, role: Role = Role.Deployer, - ) => getKeysForRole(testnetConfigs, environmentName, context, role), + ) => getKeysForRole(environmentName, supportedChainNames, context, role), agents, core, igp, diff --git a/typescript/infra/config/environments/testnet4/ism/verification.json b/typescript/infra/config/environments/testnet4/ism/verification.json index 10b6f54c1..2b2cd3331 100644 --- a/typescript/infra/config/environments/testnet4/ism/verification.json +++ b/typescript/infra/config/environments/testnet4/ism/verification.json @@ -1,1498 +1,1560 @@ { "alfajores": [ { - "name": "StaticMultisigIsmFactory", "address": "0x9AF85731EDd41E2E50F81Ef8a0A69D2fB836EDf9", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticMultisigIsmFactory" }, { - "name": "StaticAggregationIsmFactory", "address": "0xBEd8Fd6d5c6cBd878479C25f4725C7c842a43821", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticAggregationIsmFactory" }, { - "name": "DomainRoutingIsmFactory", "address": "0x98F44EA5b9cA6aa02a5B75f31E0621083d9096a2", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "DomainRoutingIsmFactory" }, { - "name": "StaticMerkleRootMultisigIsmFactory", "address": "0x6525Ac4008E38e0E70DaEf59d5f0e1721bd8aA83", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticMerkleRootMultisigIsmFactory" }, { - "name": "StaticMessageIdMultisigIsmFactory", "address": "0x4C739E01f295B70762C0bA9D86123E1775C2f703", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticMessageIdMultisigIsmFactory" }, { - "name": "StaticMerkleRootMultisigIsmFactory", "address": "0x9A574458497FCaB5E5673a2610C108725002DbA3", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticMerkleRootMultisigIsmFactory" }, { - "name": "StaticMerkleRootMultisigIsmFactory", "address": "0xa9C7e306C0941896CA1fd528aA59089571D8D67E", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticMerkleRootMultisigIsmFactory" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x826Ab153e944cE1B251aBD174071DF70d71132D7" + "address": "0x826Ab153e944cE1B251aBD174071DF70d71132D7", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticMessageIdMultisigIsmFactory", "address": "0xC1b8c0e56D6a34940Ee2B86172450B54AFd633A7", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticMessageIdMultisigIsmFactory" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0x78ebeCE0fCAa39F0F0C422E87EBd6BcB708aa763" + "address": "0x78ebeCE0fCAa39F0F0C422E87EBd6BcB708aa763", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationIsmFactory", "address": "0x4bE8AC22f506B1504C93C3A5b1579C5e7c550D9C", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticAggregationIsmFactory" }, { - "name": "StaticAggregationIsm", - "address": "0xd93F514f36d528DCDc3af1C92788f1bf1f480A7f" + "address": "0xd93F514f36d528DCDc3af1C92788f1bf1f480A7f", + "name": "StaticAggregationIsm" }, { - "name": "StaticAggregationHookFactory", "address": "0x71bB34Ee833467443628CEdFAA188B2387827Cee", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticAggregationHookFactory" }, { - "name": "StaticAggregationHook", - "address": "0x019e0a350D85eF67250dD88CD6477ad5E0c6E31e" + "address": "0x019e0a350D85eF67250dD88CD6477ad5E0c6E31e", + "name": "StaticAggregationHook" }, { - "name": "DomainRoutingIsmFactory", "address": "0x37308d498bc7B0f002cb02Cf8fA01770dC2169c8", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "DomainRoutingIsmFactory" }, { - "name": "DomaingRoutingIsm", - "address": "0x94087079Bf22D8e2BCE02e2180e738C3F21dD44E" + "address": "0x94087079Bf22D8e2BCE02e2180e738C3F21dD44E", + "name": "DomaingRoutingIsm" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x826Ab153e944cE1B251aBD174071DF70d71132D7" + "address": "0x826Ab153e944cE1B251aBD174071DF70d71132D7", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0x78ebeCE0fCAa39F0F0C422E87EBd6BcB708aa763" + "address": "0x78ebeCE0fCAa39F0F0C422E87EBd6BcB708aa763", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationIsm", - "address": "0xd93F514f36d528DCDc3af1C92788f1bf1f480A7f" + "address": "0xd93F514f36d528DCDc3af1C92788f1bf1f480A7f", + "name": "StaticAggregationIsm" }, { - "name": "StaticAggregationHook", - "address": "0x019e0a350D85eF67250dD88CD6477ad5E0c6E31e" + "address": "0x019e0a350D85eF67250dD88CD6477ad5E0c6E31e", + "name": "StaticAggregationHook" }, { - "name": "DomaingRoutingIsm", - "address": "0x94087079Bf22D8e2BCE02e2180e738C3F21dD44E" + "address": "0x94087079Bf22D8e2BCE02e2180e738C3F21dD44E", + "name": "DomaingRoutingIsm" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x826Ab153e944cE1B251aBD174071DF70d71132D7" + "address": "0x826Ab153e944cE1B251aBD174071DF70d71132D7", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0x78ebeCE0fCAa39F0F0C422E87EBd6BcB708aa763" + "address": "0x78ebeCE0fCAa39F0F0C422E87EBd6BcB708aa763", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationIsm", - "address": "0xd93F514f36d528DCDc3af1C92788f1bf1f480A7f" + "address": "0xd93F514f36d528DCDc3af1C92788f1bf1f480A7f", + "name": "StaticAggregationIsm" }, { - "name": "StaticAggregationHook", - "address": "0x019e0a350D85eF67250dD88CD6477ad5E0c6E31e" + "address": "0x019e0a350D85eF67250dD88CD6477ad5E0c6E31e", + "name": "StaticAggregationHook" }, { - "name": "DomaingRoutingIsm", - "address": "0x94087079Bf22D8e2BCE02e2180e738C3F21dD44E" + "address": "0x94087079Bf22D8e2BCE02e2180e738C3F21dD44E", + "name": "DomaingRoutingIsm" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x826Ab153e944cE1B251aBD174071DF70d71132D7" + "address": "0x826Ab153e944cE1B251aBD174071DF70d71132D7", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0x78ebeCE0fCAa39F0F0C422E87EBd6BcB708aa763" + "address": "0x78ebeCE0fCAa39F0F0C422E87EBd6BcB708aa763", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationIsm", - "address": "0xd93F514f36d528DCDc3af1C92788f1bf1f480A7f" + "address": "0xd93F514f36d528DCDc3af1C92788f1bf1f480A7f", + "name": "StaticAggregationIsm" }, { - "name": "StaticAggregationHook", - "address": "0x019e0a350D85eF67250dD88CD6477ad5E0c6E31e" + "address": "0x019e0a350D85eF67250dD88CD6477ad5E0c6E31e", + "name": "StaticAggregationHook" }, { - "name": "DomaingRoutingIsm", - "address": "0x94087079Bf22D8e2BCE02e2180e738C3F21dD44E" + "address": "0x94087079Bf22D8e2BCE02e2180e738C3F21dD44E", + "name": "DomaingRoutingIsm" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x826Ab153e944cE1B251aBD174071DF70d71132D7" + "address": "0x826Ab153e944cE1B251aBD174071DF70d71132D7", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0x78ebeCE0fCAa39F0F0C422E87EBd6BcB708aa763" + "address": "0x78ebeCE0fCAa39F0F0C422E87EBd6BcB708aa763", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationIsm", - "address": "0xd93F514f36d528DCDc3af1C92788f1bf1f480A7f" + "address": "0xd93F514f36d528DCDc3af1C92788f1bf1f480A7f", + "name": "StaticAggregationIsm" }, { - "name": "StaticAggregationHook", - "address": "0x019e0a350D85eF67250dD88CD6477ad5E0c6E31e" + "address": "0x019e0a350D85eF67250dD88CD6477ad5E0c6E31e", + "name": "StaticAggregationHook" }, { - "name": "DomaingRoutingIsm", - "address": "0x94087079Bf22D8e2BCE02e2180e738C3F21dD44E" + "address": "0x94087079Bf22D8e2BCE02e2180e738C3F21dD44E", + "name": "DomaingRoutingIsm" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x826Ab153e944cE1B251aBD174071DF70d71132D7" + "address": "0x826Ab153e944cE1B251aBD174071DF70d71132D7", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0x78ebeCE0fCAa39F0F0C422E87EBd6BcB708aa763" + "address": "0x78ebeCE0fCAa39F0F0C422E87EBd6BcB708aa763", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationIsm", - "address": "0xd93F514f36d528DCDc3af1C92788f1bf1f480A7f" + "address": "0xd93F514f36d528DCDc3af1C92788f1bf1f480A7f", + "name": "StaticAggregationIsm" }, { - "name": "StaticAggregationHook", - "address": "0x019e0a350D85eF67250dD88CD6477ad5E0c6E31e" + "address": "0x019e0a350D85eF67250dD88CD6477ad5E0c6E31e", + "name": "StaticAggregationHook" }, { - "name": "DomaingRoutingIsm", - "address": "0x94087079Bf22D8e2BCE02e2180e738C3F21dD44E" + "address": "0x94087079Bf22D8e2BCE02e2180e738C3F21dD44E", + "name": "DomaingRoutingIsm" }, { - "name": "StaticMerkleRootMultisigIsm", "address": "0x826Ab153e944cE1B251aBD174071DF70d71132D7", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticMessageIdMultisigIsm", "address": "0x78ebeCE0fCAa39F0F0C422E87EBd6BcB708aa763", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationIsm", "address": "0xd93F514f36d528DCDc3af1C92788f1bf1f480A7f", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticAggregationIsm" }, { - "name": "StaticAggregationHook", "address": "0x019e0a350D85eF67250dD88CD6477ad5E0c6E31e", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticAggregationHook" }, { - "name": "DomaingRoutingIsm", "address": "0xd5ab9FaC601Ed731d5e9c87E5977D2e5fD380666", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "DomaingRoutingIsm" }, { - "name": "StaticMerkleRootMultisigIsm", "address": "0x826Ab153e944cE1B251aBD174071DF70d71132D7", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticMessageIdMultisigIsm", "address": "0x78ebeCE0fCAa39F0F0C422E87EBd6BcB708aa763", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationIsm", "address": "0xd93F514f36d528DCDc3af1C92788f1bf1f480A7f", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticAggregationIsm" }, { - "name": "StaticAggregationHook", "address": "0x019e0a350D85eF67250dD88CD6477ad5E0c6E31e", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticAggregationHook" }, { - "name": "DomaingRoutingIsm", "address": "0xd5ab9FaC601Ed731d5e9c87E5977D2e5fD380666", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "DomaingRoutingIsm" }, { - "name": "StaticMerkleRootMultisigIsm", "address": "0x826Ab153e944cE1B251aBD174071DF70d71132D7", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticMessageIdMultisigIsm", "address": "0x78ebeCE0fCAa39F0F0C422E87EBd6BcB708aa763", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationIsm", "address": "0xd93F514f36d528DCDc3af1C92788f1bf1f480A7f", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticAggregationIsm" }, { - "name": "StaticAggregationHook", "address": "0x019e0a350D85eF67250dD88CD6477ad5E0c6E31e", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticAggregationHook" }, { - "name": "DomaingRoutingIsm", "address": "0xd5ab9FaC601Ed731d5e9c87E5977D2e5fD380666", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "DomaingRoutingIsm" }, { - "name": "StaticMerkleRootMultisigIsm", "address": "0x826Ab153e944cE1B251aBD174071DF70d71132D7", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticMessageIdMultisigIsm", "address": "0x78ebeCE0fCAa39F0F0C422E87EBd6BcB708aa763", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationIsm", "address": "0xd93F514f36d528DCDc3af1C92788f1bf1f480A7f", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticAggregationIsm" }, { - "name": "StaticAggregationHook", "address": "0x019e0a350D85eF67250dD88CD6477ad5E0c6E31e", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticAggregationHook" }, { - "name": "DomaingRoutingIsm", "address": "0xd5ab9FaC601Ed731d5e9c87E5977D2e5fD380666", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "DomaingRoutingIsm" } ], - "fuji": [ + "bsctestnet": [ { - "name": "StaticMultisigIsmFactory", - "address": "0x094652a8ea2153A03916771a778E7b66839A4F58", + "address": "0xfb6B94750e1307719892fBC21AC7A0C74A467869", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticMultisigIsmFactory" }, { - "name": "StaticAggregationIsmFactory", - "address": "0x9fB5D10C07569F2EBdc8ec4432B3a52b6d0ad9A0", + "address": "0xda72972291172B9966Dec7606d45d72e2b9f2470", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticAggregationIsmFactory" }, { - "name": "DomainRoutingIsmFactory", - "address": "0xB24C91238eA30D59CF58CEB8dd5e4eaf70544d47", + "address": "0x0CA314006fe0e7EF88ad2Bb69a7421aB2f1C5288", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "DomainRoutingIsmFactory" }, { - "name": "StaticMerkleRootMultisigIsmFactory", - "address": "0x76a1aaE73e9D837ceF10Ac5AF5AfDD30d7612f98", + "address": "0x8DA546024850D998Be3b65204c0F0f63C1f3B0A1", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticMerkleRootMultisigIsmFactory" }, { - "name": "StaticMessageIdMultisigIsmFactory", - "address": "0xA1e6d12a3F5F7e05E4D6cb39E71534F27fE29561", + "address": "0x7Bc0bb71aE0E9bDC0Ac53e932870728D95FA28bF", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticMessageIdMultisigIsmFactory" }, { - "name": "StaticMerkleRootMultisigIsmFactory", - "address": "0xc25e8cE0960fa2573AFa591484F2cA6e4497C2e5", + "address": "0x3E235B90197E1D6b5DB5ad5aD49f2c1ED6406382", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticMerkleRootMultisigIsmFactory" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x074D3C4249AFdac44B70d1D220F358Ff895F7a80" + "address": "0x9b13f3d75797951f463D61092Bba7947c8cEb25c", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticMerkleRootMultisigIsmFactory", - "address": "0x93F50Ac4E5663DAAb03508008d592f6260964f62", + "address": "0x0D96aF0c01c4bbbadaaF989Eb489c8783F35B763", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticMessageIdMultisigIsmFactory" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x0683D0258A162a87E1F25C454BaA4e9E9DE46B0a" + "address": "0x25069c25466B1F21E0D22f467a5f91bc7b24b515", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticMessageIdMultisigIsmFactory", - "address": "0x90e1F9918F304645e4F6324E5C0EAc70138C84Ce", + "address": "0x40613dE82d672605Ab051C64079022Bb4F8bDE4f", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticAggregationIsmFactory" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0xd3262339D39103a4b41379b183b0311B78c9a11F" + "address": "0x25adfb5Ddf378EC77cd5842Ff33B96277E4DD29b", + "name": "StaticAggregationIsm" }, { - "name": "StaticAggregationIsmFactory", - "address": "0xF588129ed84F219A1f0f921bE7Aa1B2176516858", + "address": "0xa1145B39F1c7Ef9aA593BC1DB1634b00CC020942", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticAggregationHookFactory" }, { - "name": "StaticAggregationIsm", - "address": "0x6716Ef4e72c74e66eB40198B16b4379a763fCEd8" + "address": "0x3938ED4c6b5a7f9947C44e58e36a864F03228175", + "name": "StaticAggregationHook" }, { - "name": "StaticAggregationHookFactory", - "address": "0x99554CC33cBCd6EDDd2f3fc9c7C9194Cb3b5df1E", + "address": "0xea12ECFD1f241da323e93F12b4ed936403990190", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "DomainRoutingIsmFactory" }, { - "name": "StaticAggregationHook", - "address": "0xBDD7Cfb61D7ae89343b428D3E527BDA9705c4EAC" + "address": "0xf9E264C10E9176c97c22790E0Ece8D98927Bca71", + "name": "DomaingRoutingIsm" }, { - "name": "DomainRoutingIsmFactory", - "address": "0xf9271189Cb30AD1F272f1A9EB2272224135B9350", - "constructorArguments": "", - "isProxy": false + "address": "0x9b13f3d75797951f463D61092Bba7947c8cEb25c", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "DomaingRoutingIsm", - "address": "0x930c36A0c978B0b86B544859692A1D94c5148204" + "address": "0x25069c25466B1F21E0D22f467a5f91bc7b24b515", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x0683D0258A162a87E1F25C454BaA4e9E9DE46B0a" + "address": "0x25adfb5Ddf378EC77cd5842Ff33B96277E4DD29b", + "name": "StaticAggregationIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0xd3262339D39103a4b41379b183b0311B78c9a11F" + "address": "0x3938ED4c6b5a7f9947C44e58e36a864F03228175", + "name": "StaticAggregationHook" }, { - "name": "StaticAggregationIsm", - "address": "0x6716Ef4e72c74e66eB40198B16b4379a763fCEd8" + "address": "0xf9E264C10E9176c97c22790E0Ece8D98927Bca71", + "name": "DomaingRoutingIsm" }, { - "name": "StaticAggregationHook", - "address": "0xBDD7Cfb61D7ae89343b428D3E527BDA9705c4EAC" + "address": "0x9b13f3d75797951f463D61092Bba7947c8cEb25c", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "DomaingRoutingIsm", - "address": "0x930c36A0c978B0b86B544859692A1D94c5148204" + "address": "0x25069c25466B1F21E0D22f467a5f91bc7b24b515", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x0683D0258A162a87E1F25C454BaA4e9E9DE46B0a" + "address": "0x25adfb5Ddf378EC77cd5842Ff33B96277E4DD29b", + "name": "StaticAggregationIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0xd3262339D39103a4b41379b183b0311B78c9a11F" + "address": "0x3938ED4c6b5a7f9947C44e58e36a864F03228175", + "name": "StaticAggregationHook" }, { - "name": "StaticAggregationIsm", - "address": "0x6716Ef4e72c74e66eB40198B16b4379a763fCEd8" + "address": "0xf9E264C10E9176c97c22790E0Ece8D98927Bca71", + "name": "DomaingRoutingIsm" }, { - "name": "StaticAggregationHook", - "address": "0xBDD7Cfb61D7ae89343b428D3E527BDA9705c4EAC" + "address": "0x9b13f3d75797951f463D61092Bba7947c8cEb25c", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "DomaingRoutingIsm", - "address": "0x930c36A0c978B0b86B544859692A1D94c5148204" + "address": "0x25069c25466B1F21E0D22f467a5f91bc7b24b515", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x0683D0258A162a87E1F25C454BaA4e9E9DE46B0a" + "address": "0x25adfb5Ddf378EC77cd5842Ff33B96277E4DD29b", + "name": "StaticAggregationIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0xd3262339D39103a4b41379b183b0311B78c9a11F" + "address": "0x3938ED4c6b5a7f9947C44e58e36a864F03228175", + "name": "StaticAggregationHook" }, { - "name": "StaticAggregationIsm", - "address": "0x6716Ef4e72c74e66eB40198B16b4379a763fCEd8" + "address": "0xf9E264C10E9176c97c22790E0Ece8D98927Bca71", + "name": "DomaingRoutingIsm" }, { - "name": "StaticAggregationHook", - "address": "0xBDD7Cfb61D7ae89343b428D3E527BDA9705c4EAC" + "address": "0x9b13f3d75797951f463D61092Bba7947c8cEb25c", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "DomaingRoutingIsm", - "address": "0x930c36A0c978B0b86B544859692A1D94c5148204" + "address": "0x25069c25466B1F21E0D22f467a5f91bc7b24b515", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x0683D0258A162a87E1F25C454BaA4e9E9DE46B0a" + "address": "0x25adfb5Ddf378EC77cd5842Ff33B96277E4DD29b", + "name": "StaticAggregationIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0xd3262339D39103a4b41379b183b0311B78c9a11F" + "address": "0x3938ED4c6b5a7f9947C44e58e36a864F03228175", + "name": "StaticAggregationHook" }, { - "name": "StaticAggregationIsm", - "address": "0x6716Ef4e72c74e66eB40198B16b4379a763fCEd8" + "address": "0xf9E264C10E9176c97c22790E0Ece8D98927Bca71", + "name": "DomaingRoutingIsm" }, { - "name": "StaticAggregationHook", - "address": "0xBDD7Cfb61D7ae89343b428D3E527BDA9705c4EAC" + "address": "0x9b13f3d75797951f463D61092Bba7947c8cEb25c", + "constructorArguments": "", + "isProxy": true, + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "DomaingRoutingIsm", - "address": "0x930c36A0c978B0b86B544859692A1D94c5148204" + "address": "0x25069c25466B1F21E0D22f467a5f91bc7b24b515", + "constructorArguments": "", + "isProxy": true, + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x0683D0258A162a87E1F25C454BaA4e9E9DE46B0a" + "address": "0x25adfb5Ddf378EC77cd5842Ff33B96277E4DD29b", + "constructorArguments": "", + "isProxy": true, + "name": "StaticAggregationIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0xd3262339D39103a4b41379b183b0311B78c9a11F" + "address": "0x3938ED4c6b5a7f9947C44e58e36a864F03228175", + "constructorArguments": "", + "isProxy": true, + "name": "StaticAggregationHook" }, { - "name": "StaticAggregationIsm", - "address": "0x6716Ef4e72c74e66eB40198B16b4379a763fCEd8" + "address": "0x9b13f3d75797951f463D61092Bba7947c8cEb25c", + "constructorArguments": "", + "isProxy": true, + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticAggregationHook", - "address": "0xBDD7Cfb61D7ae89343b428D3E527BDA9705c4EAC" + "address": "0x25069c25466B1F21E0D22f467a5f91bc7b24b515", + "constructorArguments": "", + "isProxy": true, + "name": "StaticMessageIdMultisigIsm" }, { - "name": "DomaingRoutingIsm", - "address": "0x930c36A0c978B0b86B544859692A1D94c5148204" + "address": "0x25adfb5Ddf378EC77cd5842Ff33B96277E4DD29b", + "constructorArguments": "", + "isProxy": true, + "name": "StaticAggregationIsm" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x0683D0258A162a87E1F25C454BaA4e9E9DE46B0a", + "address": "0x3938ED4c6b5a7f9947C44e58e36a864F03228175", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticAggregationHook" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0xd3262339D39103a4b41379b183b0311B78c9a11F", + "address": "0x9b13f3d75797951f463D61092Bba7947c8cEb25c", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticAggregationIsm", - "address": "0x6716Ef4e72c74e66eB40198B16b4379a763fCEd8", + "address": "0x25069c25466B1F21E0D22f467a5f91bc7b24b515", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationHook", - "address": "0xBDD7Cfb61D7ae89343b428D3E527BDA9705c4EAC", + "address": "0x25adfb5Ddf378EC77cd5842Ff33B96277E4DD29b", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticAggregationIsm" }, { - "name": "DomainRoutingIsmFactory", - "address": "0x683a81E0e1a238dcA7341e04c08d3bba6f0Cb74f", + "address": "0x3938ED4c6b5a7f9947C44e58e36a864F03228175", "constructorArguments": "", - "isProxy": false + "isProxy": true, + "name": "StaticAggregationHook" + } + ], + "fuji": [ + { + "address": "0x094652a8ea2153A03916771a778E7b66839A4F58", + "constructorArguments": "", + "isProxy": false, + "name": "StaticMultisigIsmFactory" }, { - "name": "DomaingRoutingIsm", - "address": "0xD47593C21C2429de56AcA808bb2Bbc236c6311DE", + "address": "0x9fB5D10C07569F2EBdc8ec4432B3a52b6d0ad9A0", "constructorArguments": "", - "isProxy": true + "isProxy": false, + "name": "StaticAggregationIsmFactory" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x0683D0258A162a87E1F25C454BaA4e9E9DE46B0a", + "address": "0xB24C91238eA30D59CF58CEB8dd5e4eaf70544d47", "constructorArguments": "", - "isProxy": true + "isProxy": false, + "name": "DomainRoutingIsmFactory" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0xd3262339D39103a4b41379b183b0311B78c9a11F", + "address": "0x76a1aaE73e9D837ceF10Ac5AF5AfDD30d7612f98", "constructorArguments": "", - "isProxy": true + "isProxy": false, + "name": "StaticMerkleRootMultisigIsmFactory" }, { - "name": "StaticAggregationIsm", - "address": "0x6716Ef4e72c74e66eB40198B16b4379a763fCEd8", + "address": "0xA1e6d12a3F5F7e05E4D6cb39E71534F27fE29561", "constructorArguments": "", - "isProxy": true + "isProxy": false, + "name": "StaticMessageIdMultisigIsmFactory" }, { - "name": "StaticAggregationHook", - "address": "0xBDD7Cfb61D7ae89343b428D3E527BDA9705c4EAC", + "address": "0xc25e8cE0960fa2573AFa591484F2cA6e4497C2e5", "constructorArguments": "", - "isProxy": true + "isProxy": false, + "name": "StaticMerkleRootMultisigIsmFactory" }, { - "name": "DomaingRoutingIsm", - "address": "0xD47593C21C2429de56AcA808bb2Bbc236c6311DE", + "address": "0x074D3C4249AFdac44B70d1D220F358Ff895F7a80", + "name": "StaticMerkleRootMultisigIsm" + }, + { + "address": "0x93F50Ac4E5663DAAb03508008d592f6260964f62", "constructorArguments": "", - "isProxy": true + "isProxy": false, + "name": "StaticMerkleRootMultisigIsmFactory" }, { - "name": "StaticMerkleRootMultisigIsm", "address": "0x0683D0258A162a87E1F25C454BaA4e9E9DE46B0a", + "name": "StaticMerkleRootMultisigIsm" + }, + { + "address": "0x90e1F9918F304645e4F6324E5C0EAc70138C84Ce", "constructorArguments": "", - "isProxy": true + "isProxy": false, + "name": "StaticMessageIdMultisigIsmFactory" }, { - "name": "StaticMessageIdMultisigIsm", "address": "0xd3262339D39103a4b41379b183b0311B78c9a11F", + "name": "StaticMessageIdMultisigIsm" + }, + { + "address": "0xF588129ed84F219A1f0f921bE7Aa1B2176516858", "constructorArguments": "", - "isProxy": true + "isProxy": false, + "name": "StaticAggregationIsmFactory" }, { - "name": "StaticAggregationIsm", "address": "0x6716Ef4e72c74e66eB40198B16b4379a763fCEd8", + "name": "StaticAggregationIsm" + }, + { + "address": "0x99554CC33cBCd6EDDd2f3fc9c7C9194Cb3b5df1E", "constructorArguments": "", - "isProxy": true + "isProxy": false, + "name": "StaticAggregationHookFactory" }, { - "name": "StaticAggregationHook", "address": "0xBDD7Cfb61D7ae89343b428D3E527BDA9705c4EAC", - "constructorArguments": "", - "isProxy": true + "name": "StaticAggregationHook" }, { - "name": "DomaingRoutingIsm", - "address": "0xD47593C21C2429de56AcA808bb2Bbc236c6311DE", + "address": "0xf9271189Cb30AD1F272f1A9EB2272224135B9350", "constructorArguments": "", - "isProxy": true + "isProxy": false, + "name": "DomainRoutingIsmFactory" + }, + { + "address": "0x930c36A0c978B0b86B544859692A1D94c5148204", + "name": "DomaingRoutingIsm" }, { - "name": "StaticMerkleRootMultisigIsm", "address": "0x0683D0258A162a87E1F25C454BaA4e9E9DE46B0a", - "constructorArguments": "", - "isProxy": true + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticMessageIdMultisigIsm", "address": "0xd3262339D39103a4b41379b183b0311B78c9a11F", - "constructorArguments": "", - "isProxy": true + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationIsm", "address": "0x6716Ef4e72c74e66eB40198B16b4379a763fCEd8", - "constructorArguments": "", - "isProxy": true + "name": "StaticAggregationIsm" }, { - "name": "StaticAggregationHook", "address": "0xBDD7Cfb61D7ae89343b428D3E527BDA9705c4EAC", - "constructorArguments": "", - "isProxy": true + "name": "StaticAggregationHook" }, { - "name": "DomaingRoutingIsm", - "address": "0xD47593C21C2429de56AcA808bb2Bbc236c6311DE", - "constructorArguments": "", - "isProxy": true - } - ], - "bsctestnet": [ + "address": "0x930c36A0c978B0b86B544859692A1D94c5148204", + "name": "DomaingRoutingIsm" + }, { - "name": "StaticMultisigIsmFactory", - "address": "0xfb6B94750e1307719892fBC21AC7A0C74A467869", - "constructorArguments": "", - "isProxy": false + "address": "0x0683D0258A162a87E1F25C454BaA4e9E9DE46B0a", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticAggregationIsmFactory", - "address": "0xda72972291172B9966Dec7606d45d72e2b9f2470", - "constructorArguments": "", - "isProxy": false + "address": "0xd3262339D39103a4b41379b183b0311B78c9a11F", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "DomainRoutingIsmFactory", - "address": "0x0CA314006fe0e7EF88ad2Bb69a7421aB2f1C5288", - "constructorArguments": "", - "isProxy": false + "address": "0x6716Ef4e72c74e66eB40198B16b4379a763fCEd8", + "name": "StaticAggregationIsm" }, { - "name": "StaticMerkleRootMultisigIsmFactory", - "address": "0x8DA546024850D998Be3b65204c0F0f63C1f3B0A1", - "constructorArguments": "", - "isProxy": false + "address": "0xBDD7Cfb61D7ae89343b428D3E527BDA9705c4EAC", + "name": "StaticAggregationHook" }, { - "name": "StaticMessageIdMultisigIsmFactory", - "address": "0x7Bc0bb71aE0E9bDC0Ac53e932870728D95FA28bF", - "constructorArguments": "", - "isProxy": false + "address": "0x930c36A0c978B0b86B544859692A1D94c5148204", + "name": "DomaingRoutingIsm" }, { - "name": "StaticMerkleRootMultisigIsmFactory", - "address": "0x3E235B90197E1D6b5DB5ad5aD49f2c1ED6406382", - "constructorArguments": "", - "isProxy": false + "address": "0x0683D0258A162a87E1F25C454BaA4e9E9DE46B0a", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x9b13f3d75797951f463D61092Bba7947c8cEb25c" + "address": "0xd3262339D39103a4b41379b183b0311B78c9a11F", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticMessageIdMultisigIsmFactory", - "address": "0x0D96aF0c01c4bbbadaaF989Eb489c8783F35B763", - "constructorArguments": "", - "isProxy": false + "address": "0x6716Ef4e72c74e66eB40198B16b4379a763fCEd8", + "name": "StaticAggregationIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0x25069c25466B1F21E0D22f467a5f91bc7b24b515" + "address": "0xBDD7Cfb61D7ae89343b428D3E527BDA9705c4EAC", + "name": "StaticAggregationHook" }, { - "name": "StaticAggregationIsmFactory", - "address": "0x40613dE82d672605Ab051C64079022Bb4F8bDE4f", - "constructorArguments": "", - "isProxy": false + "address": "0x930c36A0c978B0b86B544859692A1D94c5148204", + "name": "DomaingRoutingIsm" }, { - "name": "StaticAggregationIsm", - "address": "0x25adfb5Ddf378EC77cd5842Ff33B96277E4DD29b" + "address": "0x0683D0258A162a87E1F25C454BaA4e9E9DE46B0a", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticAggregationHookFactory", - "address": "0xa1145B39F1c7Ef9aA593BC1DB1634b00CC020942", - "constructorArguments": "", - "isProxy": false + "address": "0xd3262339D39103a4b41379b183b0311B78c9a11F", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationHook", - "address": "0x3938ED4c6b5a7f9947C44e58e36a864F03228175" + "address": "0x6716Ef4e72c74e66eB40198B16b4379a763fCEd8", + "name": "StaticAggregationIsm" }, { - "name": "DomainRoutingIsmFactory", - "address": "0xea12ECFD1f241da323e93F12b4ed936403990190", - "constructorArguments": "", - "isProxy": false + "address": "0xBDD7Cfb61D7ae89343b428D3E527BDA9705c4EAC", + "name": "StaticAggregationHook" }, { - "name": "DomaingRoutingIsm", - "address": "0xf9E264C10E9176c97c22790E0Ece8D98927Bca71" + "address": "0x930c36A0c978B0b86B544859692A1D94c5148204", + "name": "DomaingRoutingIsm" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x9b13f3d75797951f463D61092Bba7947c8cEb25c" + "address": "0x0683D0258A162a87E1F25C454BaA4e9E9DE46B0a", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0x25069c25466B1F21E0D22f467a5f91bc7b24b515" + "address": "0xd3262339D39103a4b41379b183b0311B78c9a11F", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationIsm", - "address": "0x25adfb5Ddf378EC77cd5842Ff33B96277E4DD29b" + "address": "0x6716Ef4e72c74e66eB40198B16b4379a763fCEd8", + "name": "StaticAggregationIsm" }, { - "name": "StaticAggregationHook", - "address": "0x3938ED4c6b5a7f9947C44e58e36a864F03228175" + "address": "0xBDD7Cfb61D7ae89343b428D3E527BDA9705c4EAC", + "name": "StaticAggregationHook" }, { - "name": "DomaingRoutingIsm", - "address": "0xf9E264C10E9176c97c22790E0Ece8D98927Bca71" + "address": "0x930c36A0c978B0b86B544859692A1D94c5148204", + "name": "DomaingRoutingIsm" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x9b13f3d75797951f463D61092Bba7947c8cEb25c" + "address": "0x0683D0258A162a87E1F25C454BaA4e9E9DE46B0a", + "constructorArguments": "", + "isProxy": true, + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0x25069c25466B1F21E0D22f467a5f91bc7b24b515" + "address": "0xd3262339D39103a4b41379b183b0311B78c9a11F", + "constructorArguments": "", + "isProxy": true, + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationIsm", - "address": "0x25adfb5Ddf378EC77cd5842Ff33B96277E4DD29b" + "address": "0x6716Ef4e72c74e66eB40198B16b4379a763fCEd8", + "constructorArguments": "", + "isProxy": true, + "name": "StaticAggregationIsm" }, { - "name": "StaticAggregationHook", - "address": "0x3938ED4c6b5a7f9947C44e58e36a864F03228175" + "address": "0xBDD7Cfb61D7ae89343b428D3E527BDA9705c4EAC", + "constructorArguments": "", + "isProxy": true, + "name": "StaticAggregationHook" }, { - "name": "DomaingRoutingIsm", - "address": "0xf9E264C10E9176c97c22790E0Ece8D98927Bca71" + "address": "0x683a81E0e1a238dcA7341e04c08d3bba6f0Cb74f", + "constructorArguments": "", + "isProxy": false, + "name": "DomainRoutingIsmFactory" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x9b13f3d75797951f463D61092Bba7947c8cEb25c" + "address": "0xD47593C21C2429de56AcA808bb2Bbc236c6311DE", + "constructorArguments": "", + "isProxy": true, + "name": "DomaingRoutingIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0x25069c25466B1F21E0D22f467a5f91bc7b24b515" + "address": "0x0683D0258A162a87E1F25C454BaA4e9E9DE46B0a", + "constructorArguments": "", + "isProxy": true, + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticAggregationIsm", - "address": "0x25adfb5Ddf378EC77cd5842Ff33B96277E4DD29b" + "address": "0xd3262339D39103a4b41379b183b0311B78c9a11F", + "constructorArguments": "", + "isProxy": true, + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationHook", - "address": "0x3938ED4c6b5a7f9947C44e58e36a864F03228175" + "address": "0x6716Ef4e72c74e66eB40198B16b4379a763fCEd8", + "constructorArguments": "", + "isProxy": true, + "name": "StaticAggregationIsm" }, { - "name": "DomaingRoutingIsm", - "address": "0xf9E264C10E9176c97c22790E0Ece8D98927Bca71" + "address": "0xBDD7Cfb61D7ae89343b428D3E527BDA9705c4EAC", + "constructorArguments": "", + "isProxy": true, + "name": "StaticAggregationHook" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x9b13f3d75797951f463D61092Bba7947c8cEb25c" + "address": "0xD47593C21C2429de56AcA808bb2Bbc236c6311DE", + "constructorArguments": "", + "isProxy": true, + "name": "DomaingRoutingIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0x25069c25466B1F21E0D22f467a5f91bc7b24b515" + "address": "0x0683D0258A162a87E1F25C454BaA4e9E9DE46B0a", + "constructorArguments": "", + "isProxy": true, + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticAggregationIsm", - "address": "0x25adfb5Ddf378EC77cd5842Ff33B96277E4DD29b" + "address": "0xd3262339D39103a4b41379b183b0311B78c9a11F", + "constructorArguments": "", + "isProxy": true, + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationHook", - "address": "0x3938ED4c6b5a7f9947C44e58e36a864F03228175" + "address": "0x6716Ef4e72c74e66eB40198B16b4379a763fCEd8", + "constructorArguments": "", + "isProxy": true, + "name": "StaticAggregationIsm" }, { - "name": "DomaingRoutingIsm", - "address": "0xf9E264C10E9176c97c22790E0Ece8D98927Bca71" + "address": "0xBDD7Cfb61D7ae89343b428D3E527BDA9705c4EAC", + "constructorArguments": "", + "isProxy": true, + "name": "StaticAggregationHook" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x9b13f3d75797951f463D61092Bba7947c8cEb25c", + "address": "0xD47593C21C2429de56AcA808bb2Bbc236c6311DE", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "DomaingRoutingIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0x25069c25466B1F21E0D22f467a5f91bc7b24b515", + "address": "0x0683D0258A162a87E1F25C454BaA4e9E9DE46B0a", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticAggregationIsm", - "address": "0x25adfb5Ddf378EC77cd5842Ff33B96277E4DD29b", + "address": "0xd3262339D39103a4b41379b183b0311B78c9a11F", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationHook", - "address": "0x3938ED4c6b5a7f9947C44e58e36a864F03228175", + "address": "0x6716Ef4e72c74e66eB40198B16b4379a763fCEd8", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticAggregationIsm" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x9b13f3d75797951f463D61092Bba7947c8cEb25c", + "address": "0xBDD7Cfb61D7ae89343b428D3E527BDA9705c4EAC", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticAggregationHook" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0x25069c25466B1F21E0D22f467a5f91bc7b24b515", + "address": "0xD47593C21C2429de56AcA808bb2Bbc236c6311DE", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "DomaingRoutingIsm" + } + ], + "holesky": [ + { + "address": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", + "constructorArguments": "", + "isProxy": false, + "name": "StaticMerkleRootMultisigIsmFactory" }, { - "name": "StaticAggregationIsm", - "address": "0x25adfb5Ddf378EC77cd5842Ff33B96277E4DD29b", + "address": "0x7fFe8C9c17F46F94D784E148FbadD4bF66477722", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticAggregationHook", - "address": "0x3938ED4c6b5a7f9947C44e58e36a864F03228175", + "address": "0x6966b0E55883d49BFB24539356a2f8A673E02039", "constructorArguments": "", - "isProxy": true + "isProxy": false, + "name": "StaticMessageIdMultisigIsmFactory" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x9b13f3d75797951f463D61092Bba7947c8cEb25c", + "address": "0x4863236F3a05A1A1F0850fF8cd09afeBAE82d953", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0x25069c25466B1F21E0D22f467a5f91bc7b24b515", + "address": "0x54148470292C24345fb828B003461a9444414517", "constructorArguments": "", - "isProxy": true + "isProxy": false, + "name": "StaticAggregationIsmFactory" }, { - "name": "StaticAggregationIsm", - "address": "0x25adfb5Ddf378EC77cd5842Ff33B96277E4DD29b", + "address": "0x10c9FF6EEE4BaD29734322467f541C84001422C2", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticAggregationIsm" }, { - "name": "StaticAggregationHook", - "address": "0x3938ED4c6b5a7f9947C44e58e36a864F03228175", + "address": "0x589C201a07c26b4725A4A829d772f24423da480B", + "constructorArguments": "", + "isProxy": false, + "name": "StaticAggregationHookFactory" + }, + { + "address": "0xD6B8D6C372e07f67FeAb75403c0Ec88E3cce7Ab7", + "constructorArguments": "", + "isProxy": true, + "name": "StaticAggregationHook" + }, + { + "address": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD", "constructorArguments": "", - "isProxy": true + "isProxy": false, + "name": "DomainRoutingIsmFactory" + }, + { + "address": "0x8CF9aB5F805072A007dAd0ae56E0099e83B14b61", + "constructorArguments": "", + "isProxy": true, + "name": "DomaingRoutingIsm" } ], - "sepolia": [ + "moonbasealpha": [ { - "name": "StaticMultisigIsmFactory", - "address": "0xD356C996277eFb7f75Ee8bd61b31cC781A12F54f", + "address": "0x4266D8Dd66D8Eb3934c8942968d1e54214D072d3", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticMultisigIsmFactory" }, { - "name": "StaticAggregationIsmFactory", - "address": "0x01812D60958798695391dacF092BAc4a715B1718", + "address": "0x759c4Eb4575B651a9f0Fb46653dd7B2F32fD7310", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticAggregationIsmFactory" }, { - "name": "DomainRoutingIsmFactory", - "address": "0xE67CfA164cDa449Ae38a0a09391eF6bCDf8e4e2c", + "address": "0x561331FafB7f2ABa77E77780178ADdD1A37bdaBD", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "DomainRoutingIsmFactory" }, { - "name": "StaticMerkleRootMultisigIsmFactory", - "address": "0xA9999B4abC373FF2BB95B8725FABC96CA883d811", + "address": "0x0616A79374e81eB1d2275eCe5837aD050f9c53f1", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticMerkleRootMultisigIsmFactory" }, { - "name": "StaticMessageIdMultisigIsmFactory", - "address": "0xCCC126d96efcc342BF2781A7d224D3AB1F25B19C", + "address": "0x3D696c38Dd958e635f9077e65b64aA9cf7c92627", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticMessageIdMultisigIsmFactory" }, { - "name": "StaticMerkleRootMultisigIsmFactory", - "address": "0x0a71AcC99967829eE305a285750017C4916Ca269", + "address": "0xA59Ba0A8D4ea5A5DC9c8B0101ba7E6eE6C3399A4", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticMerkleRootMultisigIsmFactory" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0xbe94Cd5d0973dB4A6E63Cb9e6b2b2042D99F9Bcd" + "address": "0x97B07A8BCc0d212A4C76679b303A4C6441BB6186", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticMessageIdMultisigIsmFactory", - "address": "0xFEb9585b2f948c1eD74034205a7439261a9d27DD", + "address": "0x8f919348F9C4619A196Acb5e377f49E5E2C0B569", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticMessageIdMultisigIsmFactory" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0x218C5DFb7C2bC1e051FCFebBa8C6D66Dda28617E" + "address": "0xb1Fc72f251A6b6c37CD4Ef8f9287578BCc7CE9Eb", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationIsmFactory", - "address": "0xC83e12EF2627ACE445C298e6eC418684918a6002", + "address": "0x0048FaB53526D9a0478f66D660059E3E3611FE3E", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticAggregationIsmFactory" }, { - "name": "StaticAggregationIsm", - "address": "0x3dD1aB6B71EBfEd239869365FE5B8b47E0507d89" + "address": "0x13b6Cb8d51C7e15e5fFee432CBB1d4614972A87e", + "name": "StaticAggregationIsm" }, { - "name": "StaticAggregationHookFactory", - "address": "0x160C28C92cA453570aD7C031972b58d5Dd128F72", + "address": "0x00DFB81Bfc45fa03060b605273147F274ea807E5", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticAggregationHookFactory" }, { - "name": "StaticAggregationHook", - "address": "0x4BC580FF77Dd1Eb320E96D34FddC97F0F04D3feD" + "address": "0x713496A1477a08230E526a4e9449FCCE4d6523e9", + "name": "StaticAggregationHook" }, { - "name": "DomainRoutingIsmFactory", - "address": "0x3603458990BfEb30f99E61B58427d196814D8ce1", + "address": "0x385C7f179168f5Da92c72E17AE8EF50F3874077f", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "DomainRoutingIsmFactory" }, { - "name": "DomaingRoutingIsm", - "address": "0x67543F48DBe592F1A15FD9aE37b1AbD104c70cf3" + "address": "0xCd76aC44337BbA003bb4Bed2Ac37604148F36bA9", + "name": "DomaingRoutingIsm" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0xbe94Cd5d0973dB4A6E63Cb9e6b2b2042D99F9Bcd" + "address": "0x97B07A8BCc0d212A4C76679b303A4C6441BB6186", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0x218C5DFb7C2bC1e051FCFebBa8C6D66Dda28617E" + "address": "0xb1Fc72f251A6b6c37CD4Ef8f9287578BCc7CE9Eb", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationIsm", - "address": "0x3dD1aB6B71EBfEd239869365FE5B8b47E0507d89" + "address": "0x13b6Cb8d51C7e15e5fFee432CBB1d4614972A87e", + "name": "StaticAggregationIsm" }, { - "name": "StaticAggregationHook", - "address": "0x4BC580FF77Dd1Eb320E96D34FddC97F0F04D3feD" + "address": "0x713496A1477a08230E526a4e9449FCCE4d6523e9", + "name": "StaticAggregationHook" }, { - "name": "DomaingRoutingIsm", - "address": "0x67543F48DBe592F1A15FD9aE37b1AbD104c70cf3" + "address": "0xCd76aC44337BbA003bb4Bed2Ac37604148F36bA9", + "name": "DomaingRoutingIsm" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0xbe94Cd5d0973dB4A6E63Cb9e6b2b2042D99F9Bcd" + "address": "0x97B07A8BCc0d212A4C76679b303A4C6441BB6186", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0x218C5DFb7C2bC1e051FCFebBa8C6D66Dda28617E" + "address": "0xb1Fc72f251A6b6c37CD4Ef8f9287578BCc7CE9Eb", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationIsm", - "address": "0x3dD1aB6B71EBfEd239869365FE5B8b47E0507d89" + "address": "0x13b6Cb8d51C7e15e5fFee432CBB1d4614972A87e", + "name": "StaticAggregationIsm" }, { - "name": "StaticAggregationHook", - "address": "0x4BC580FF77Dd1Eb320E96D34FddC97F0F04D3feD" + "address": "0x713496A1477a08230E526a4e9449FCCE4d6523e9", + "name": "StaticAggregationHook" }, { - "name": "DomaingRoutingIsm", - "address": "0x67543F48DBe592F1A15FD9aE37b1AbD104c70cf3" + "address": "0xCd76aC44337BbA003bb4Bed2Ac37604148F36bA9", + "name": "DomaingRoutingIsm" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0xbe94Cd5d0973dB4A6E63Cb9e6b2b2042D99F9Bcd" + "address": "0x97B07A8BCc0d212A4C76679b303A4C6441BB6186", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0x218C5DFb7C2bC1e051FCFebBa8C6D66Dda28617E" + "address": "0xb1Fc72f251A6b6c37CD4Ef8f9287578BCc7CE9Eb", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationIsm", - "address": "0x3dD1aB6B71EBfEd239869365FE5B8b47E0507d89" + "address": "0x13b6Cb8d51C7e15e5fFee432CBB1d4614972A87e", + "name": "StaticAggregationIsm" }, { - "name": "StaticAggregationHook", - "address": "0x4BC580FF77Dd1Eb320E96D34FddC97F0F04D3feD" + "address": "0x713496A1477a08230E526a4e9449FCCE4d6523e9", + "name": "StaticAggregationHook" }, { - "name": "DomaingRoutingIsm", - "address": "0x67543F48DBe592F1A15FD9aE37b1AbD104c70cf3" + "address": "0xCd76aC44337BbA003bb4Bed2Ac37604148F36bA9", + "name": "DomaingRoutingIsm" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0xbe94Cd5d0973dB4A6E63Cb9e6b2b2042D99F9Bcd" + "address": "0x97B07A8BCc0d212A4C76679b303A4C6441BB6186", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0x218C5DFb7C2bC1e051FCFebBa8C6D66Dda28617E" + "address": "0xb1Fc72f251A6b6c37CD4Ef8f9287578BCc7CE9Eb", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationIsm", - "address": "0x3dD1aB6B71EBfEd239869365FE5B8b47E0507d89" + "address": "0x13b6Cb8d51C7e15e5fFee432CBB1d4614972A87e", + "name": "StaticAggregationIsm" }, { - "name": "StaticAggregationHook", - "address": "0x4BC580FF77Dd1Eb320E96D34FddC97F0F04D3feD" + "address": "0x713496A1477a08230E526a4e9449FCCE4d6523e9", + "name": "StaticAggregationHook" }, { - "name": "DomaingRoutingIsm", - "address": "0x67543F48DBe592F1A15FD9aE37b1AbD104c70cf3" + "address": "0xCd76aC44337BbA003bb4Bed2Ac37604148F36bA9", + "name": "DomaingRoutingIsm" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0xbe94Cd5d0973dB4A6E63Cb9e6b2b2042D99F9Bcd", + "address": "0x97B07A8BCc0d212A4C76679b303A4C6441BB6186", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0x218C5DFb7C2bC1e051FCFebBa8C6D66Dda28617E", + "address": "0xb1Fc72f251A6b6c37CD4Ef8f9287578BCc7CE9Eb", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationIsm", - "address": "0x3dD1aB6B71EBfEd239869365FE5B8b47E0507d89", + "address": "0x13b6Cb8d51C7e15e5fFee432CBB1d4614972A87e", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticAggregationIsm" }, { - "name": "StaticAggregationHook", - "address": "0x4BC580FF77Dd1Eb320E96D34FddC97F0F04D3feD", + "address": "0x713496A1477a08230E526a4e9449FCCE4d6523e9", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticAggregationHook" }, { - "name": "DomainRoutingIsmFactory", - "address": "0x3F100cBBE5FD5466BdB4B3a15Ac226957e7965Ad", + "address": "0xE8F752e5C4E1A6a2e3eAfa42d44D601A22d78f2b", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "DomainRoutingIsmFactory" }, { - "name": "DomaingRoutingIsm", - "address": "0x9b0CC3BD9030CE269EF3124Bb36Cf954a490688e", + "address": "0x063C2D908EAb532Cd207F309F0fd176ae6b2e1d1", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "DomaingRoutingIsm" } ], - "moonbasealpha": [ + "plumetestnet": [ { - "name": "StaticMultisigIsmFactory", - "address": "0x4266D8Dd66D8Eb3934c8942968d1e54214D072d3", + "address": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticMerkleRootMultisigIsmFactory" }, { - "name": "StaticAggregationIsmFactory", - "address": "0x759c4Eb4575B651a9f0Fb46653dd7B2F32fD7310", + "address": "0x87935eB971eaA9826060261b07a919451dfd0409", "constructorArguments": "", - "isProxy": false + "isProxy": true, + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "DomainRoutingIsmFactory", - "address": "0x561331FafB7f2ABa77E77780178ADdD1A37bdaBD", + "address": "0x44b764045BfDC68517e10e783E69B376cef196B2", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticMessageIdMultisigIsmFactory" }, { - "name": "StaticMerkleRootMultisigIsmFactory", - "address": "0x0616A79374e81eB1d2275eCe5837aD050f9c53f1", + "address": "0xE5cA56294dA5Bd490D5Bc489B177B002ad16AF83", "constructorArguments": "", - "isProxy": false + "isProxy": true, + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticMessageIdMultisigIsmFactory", - "address": "0x3D696c38Dd958e635f9077e65b64aA9cf7c92627", + "address": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticAggregationIsmFactory" }, { - "name": "StaticMerkleRootMultisigIsmFactory", - "address": "0xA59Ba0A8D4ea5A5DC9c8B0101ba7E6eE6C3399A4", + "address": "0x7fFe8C9c17F46F94D784E148FbadD4bF66477722", "constructorArguments": "", - "isProxy": false + "isProxy": true, + "name": "StaticAggregationIsm" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x97B07A8BCc0d212A4C76679b303A4C6441BB6186" + "address": "0x6966b0E55883d49BFB24539356a2f8A673E02039", + "constructorArguments": "", + "isProxy": false, + "name": "StaticAggregationHookFactory" }, { - "name": "StaticMessageIdMultisigIsmFactory", - "address": "0x8f919348F9C4619A196Acb5e377f49E5E2C0B569", + "address": "0x4863236F3a05A1A1F0850fF8cd09afeBAE82d953", "constructorArguments": "", - "isProxy": false + "isProxy": true, + "name": "StaticAggregationHook" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0xb1Fc72f251A6b6c37CD4Ef8f9287578BCc7CE9Eb" + "address": "0x54148470292C24345fb828B003461a9444414517", + "constructorArguments": "", + "isProxy": false, + "name": "DomainRoutingIsmFactory" }, { - "name": "StaticAggregationIsmFactory", - "address": "0x0048FaB53526D9a0478f66D660059E3E3611FE3E", + "address": "0x10c9FF6EEE4BaD29734322467f541C84001422C2", + "constructorArguments": "", + "isProxy": true, + "name": "DomaingRoutingIsm" + } + ], + "scrollsepolia": [ + { + "address": "0x6E7b29CB2A7617405B4d30C6f84bBD51b4Bb4be8", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticMerkleRootMultisigIsmFactory" }, { - "name": "StaticAggregationIsm", - "address": "0x13b6Cb8d51C7e15e5fFee432CBB1d4614972A87e" + "address": "0x209e7F9d40954E230008B9bb076a0901d32695e5", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticAggregationHookFactory", - "address": "0x00DFB81Bfc45fa03060b605273147F274ea807E5", + "address": "0x275aCcCa81cAD931dC6fB6E49ED233Bc99Bed4A7", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticMerkleRootMultisigIsmFactory" }, { - "name": "StaticAggregationHook", - "address": "0x713496A1477a08230E526a4e9449FCCE4d6523e9" + "address": "0x33999AB153F68D481AAB1B238368Ffd1Fe81F360", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "DomainRoutingIsmFactory", - "address": "0x385C7f179168f5Da92c72E17AE8EF50F3874077f", + "address": "0xeb6f11189197223c656807a83B0DD374f9A6dF44", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticMessageIdMultisigIsmFactory" }, { - "name": "DomaingRoutingIsm", - "address": "0xCd76aC44337BbA003bb4Bed2Ac37604148F36bA9" + "address": "0x3e6F45B03314bD21BcE4201666d483291575E391", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x97B07A8BCc0d212A4C76679b303A4C6441BB6186" + "address": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", + "constructorArguments": "", + "isProxy": false, + "name": "StaticAggregationIsmFactory" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0xb1Fc72f251A6b6c37CD4Ef8f9287578BCc7CE9Eb" + "address": "0x87935eB971eaA9826060261b07a919451dfd0409", + "name": "StaticAggregationIsm" }, { - "name": "StaticAggregationIsm", - "address": "0x13b6Cb8d51C7e15e5fFee432CBB1d4614972A87e" + "address": "0x44b764045BfDC68517e10e783E69B376cef196B2", + "constructorArguments": "", + "isProxy": false, + "name": "StaticAggregationHookFactory" }, { - "name": "StaticAggregationHook", - "address": "0x713496A1477a08230E526a4e9449FCCE4d6523e9" + "address": "0xE5cA56294dA5Bd490D5Bc489B177B002ad16AF83", + "name": "StaticAggregationHook" }, { - "name": "DomaingRoutingIsm", - "address": "0xCd76aC44337BbA003bb4Bed2Ac37604148F36bA9" + "address": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", + "constructorArguments": "", + "isProxy": false, + "name": "DomainRoutingIsmFactory" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x97B07A8BCc0d212A4C76679b303A4C6441BB6186" + "address": "0x7fFe8C9c17F46F94D784E148FbadD4bF66477722", + "name": "DomaingRoutingIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0xb1Fc72f251A6b6c37CD4Ef8f9287578BCc7CE9Eb" + "address": "0x33999AB153F68D481AAB1B238368Ffd1Fe81F360", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticAggregationIsm", - "address": "0x13b6Cb8d51C7e15e5fFee432CBB1d4614972A87e" + "address": "0x3e6F45B03314bD21BcE4201666d483291575E391", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationHook", - "address": "0x713496A1477a08230E526a4e9449FCCE4d6523e9" + "address": "0x87935eB971eaA9826060261b07a919451dfd0409", + "name": "StaticAggregationIsm" }, { - "name": "DomaingRoutingIsm", - "address": "0xCd76aC44337BbA003bb4Bed2Ac37604148F36bA9" + "address": "0xE5cA56294dA5Bd490D5Bc489B177B002ad16AF83", + "name": "StaticAggregationHook" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x97B07A8BCc0d212A4C76679b303A4C6441BB6186" + "address": "0x7fFe8C9c17F46F94D784E148FbadD4bF66477722", + "name": "DomaingRoutingIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0xb1Fc72f251A6b6c37CD4Ef8f9287578BCc7CE9Eb" + "address": "0x33999AB153F68D481AAB1B238368Ffd1Fe81F360", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticAggregationIsm", - "address": "0x13b6Cb8d51C7e15e5fFee432CBB1d4614972A87e" + "address": "0x3e6F45B03314bD21BcE4201666d483291575E391", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationHook", - "address": "0x713496A1477a08230E526a4e9449FCCE4d6523e9" + "address": "0x87935eB971eaA9826060261b07a919451dfd0409", + "name": "StaticAggregationIsm" }, { - "name": "DomaingRoutingIsm", - "address": "0xCd76aC44337BbA003bb4Bed2Ac37604148F36bA9" + "address": "0xE5cA56294dA5Bd490D5Bc489B177B002ad16AF83", + "name": "StaticAggregationHook" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x97B07A8BCc0d212A4C76679b303A4C6441BB6186" + "address": "0x7fFe8C9c17F46F94D784E148FbadD4bF66477722", + "name": "DomaingRoutingIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0xb1Fc72f251A6b6c37CD4Ef8f9287578BCc7CE9Eb" + "address": "0x33999AB153F68D481AAB1B238368Ffd1Fe81F360", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticAggregationIsm", - "address": "0x13b6Cb8d51C7e15e5fFee432CBB1d4614972A87e" + "address": "0x3e6F45B03314bD21BcE4201666d483291575E391", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationHook", - "address": "0x713496A1477a08230E526a4e9449FCCE4d6523e9" + "address": "0x87935eB971eaA9826060261b07a919451dfd0409", + "name": "StaticAggregationIsm" }, { - "name": "DomaingRoutingIsm", - "address": "0xCd76aC44337BbA003bb4Bed2Ac37604148F36bA9" + "address": "0xE5cA56294dA5Bd490D5Bc489B177B002ad16AF83", + "name": "StaticAggregationHook" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x97B07A8BCc0d212A4C76679b303A4C6441BB6186", - "constructorArguments": "", - "isProxy": true + "address": "0x7fFe8C9c17F46F94D784E148FbadD4bF66477722", + "name": "DomaingRoutingIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0xb1Fc72f251A6b6c37CD4Ef8f9287578BCc7CE9Eb", - "constructorArguments": "", - "isProxy": true + "address": "0x33999AB153F68D481AAB1B238368Ffd1Fe81F360", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticAggregationIsm", - "address": "0x13b6Cb8d51C7e15e5fFee432CBB1d4614972A87e", - "constructorArguments": "", - "isProxy": true + "address": "0x3e6F45B03314bD21BcE4201666d483291575E391", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationHook", - "address": "0x713496A1477a08230E526a4e9449FCCE4d6523e9", - "constructorArguments": "", - "isProxy": true + "address": "0x87935eB971eaA9826060261b07a919451dfd0409", + "name": "StaticAggregationIsm" }, { - "name": "DomainRoutingIsmFactory", - "address": "0xE8F752e5C4E1A6a2e3eAfa42d44D601A22d78f2b", - "constructorArguments": "", - "isProxy": false + "address": "0xE5cA56294dA5Bd490D5Bc489B177B002ad16AF83", + "name": "StaticAggregationHook" }, { - "name": "DomaingRoutingIsm", - "address": "0x063C2D908EAb532Cd207F309F0fd176ae6b2e1d1", - "constructorArguments": "", - "isProxy": true - } - ], - "scrollsepolia": [ + "address": "0x7fFe8C9c17F46F94D784E148FbadD4bF66477722", + "name": "DomaingRoutingIsm" + }, { - "name": "StaticMerkleRootMultisigIsmFactory", - "address": "0x6E7b29CB2A7617405B4d30C6f84bBD51b4Bb4be8", + "address": "0x33999AB153F68D481AAB1B238368Ffd1Fe81F360", "constructorArguments": "", - "isProxy": false + "isProxy": true, + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x209e7F9d40954E230008B9bb076a0901d32695e5" + "address": "0x3e6F45B03314bD21BcE4201666d483291575E391", + "constructorArguments": "", + "isProxy": true, + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticMerkleRootMultisigIsmFactory", - "address": "0x275aCcCa81cAD931dC6fB6E49ED233Bc99Bed4A7", + "address": "0x87935eB971eaA9826060261b07a919451dfd0409", "constructorArguments": "", - "isProxy": false + "isProxy": true, + "name": "StaticAggregationIsm" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x33999AB153F68D481AAB1B238368Ffd1Fe81F360" + "address": "0xE5cA56294dA5Bd490D5Bc489B177B002ad16AF83", + "constructorArguments": "", + "isProxy": true, + "name": "StaticAggregationHook" }, { - "name": "StaticMessageIdMultisigIsmFactory", - "address": "0xeb6f11189197223c656807a83B0DD374f9A6dF44", + "address": "0x17866ebE0e503784a9461d3e753dEeD0d3F61153", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "DomainRoutingIsmFactory" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0x3e6F45B03314bD21BcE4201666d483291575E391" - }, + "address": "0xea80345322520d37770dbDeD3FE9c53ba93E70D8", + "constructorArguments": "", + "isProxy": true, + "name": "DomaingRoutingIsm" + } + ], + "sepolia": [ { - "name": "StaticAggregationIsmFactory", - "address": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", + "address": "0xD356C996277eFb7f75Ee8bd61b31cC781A12F54f", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticMultisigIsmFactory" }, { - "name": "StaticAggregationIsm", - "address": "0x87935eB971eaA9826060261b07a919451dfd0409" + "address": "0x01812D60958798695391dacF092BAc4a715B1718", + "constructorArguments": "", + "isProxy": false, + "name": "StaticAggregationIsmFactory" }, { - "name": "StaticAggregationHookFactory", - "address": "0x44b764045BfDC68517e10e783E69B376cef196B2", + "address": "0xE67CfA164cDa449Ae38a0a09391eF6bCDf8e4e2c", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "DomainRoutingIsmFactory" }, { - "name": "StaticAggregationHook", - "address": "0xE5cA56294dA5Bd490D5Bc489B177B002ad16AF83" + "address": "0xA9999B4abC373FF2BB95B8725FABC96CA883d811", + "constructorArguments": "", + "isProxy": false, + "name": "StaticMerkleRootMultisigIsmFactory" }, { - "name": "DomainRoutingIsmFactory", - "address": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", + "address": "0xCCC126d96efcc342BF2781A7d224D3AB1F25B19C", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticMessageIdMultisigIsmFactory" }, { - "name": "DomaingRoutingIsm", - "address": "0x7fFe8C9c17F46F94D784E148FbadD4bF66477722" + "address": "0x0a71AcC99967829eE305a285750017C4916Ca269", + "constructorArguments": "", + "isProxy": false, + "name": "StaticMerkleRootMultisigIsmFactory" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x33999AB153F68D481AAB1B238368Ffd1Fe81F360" + "address": "0xbe94Cd5d0973dB4A6E63Cb9e6b2b2042D99F9Bcd", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0x3e6F45B03314bD21BcE4201666d483291575E391" + "address": "0xFEb9585b2f948c1eD74034205a7439261a9d27DD", + "constructorArguments": "", + "isProxy": false, + "name": "StaticMessageIdMultisigIsmFactory" }, { - "name": "StaticAggregationIsm", - "address": "0x87935eB971eaA9826060261b07a919451dfd0409" + "address": "0x218C5DFb7C2bC1e051FCFebBa8C6D66Dda28617E", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationHook", - "address": "0xE5cA56294dA5Bd490D5Bc489B177B002ad16AF83" + "address": "0xC83e12EF2627ACE445C298e6eC418684918a6002", + "constructorArguments": "", + "isProxy": false, + "name": "StaticAggregationIsmFactory" }, { - "name": "DomaingRoutingIsm", - "address": "0x7fFe8C9c17F46F94D784E148FbadD4bF66477722" + "address": "0x3dD1aB6B71EBfEd239869365FE5B8b47E0507d89", + "name": "StaticAggregationIsm" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x33999AB153F68D481AAB1B238368Ffd1Fe81F360" + "address": "0x160C28C92cA453570aD7C031972b58d5Dd128F72", + "constructorArguments": "", + "isProxy": false, + "name": "StaticAggregationHookFactory" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0x3e6F45B03314bD21BcE4201666d483291575E391" + "address": "0x4BC580FF77Dd1Eb320E96D34FddC97F0F04D3feD", + "name": "StaticAggregationHook" }, { - "name": "StaticAggregationIsm", - "address": "0x87935eB971eaA9826060261b07a919451dfd0409" + "address": "0x3603458990BfEb30f99E61B58427d196814D8ce1", + "constructorArguments": "", + "isProxy": false, + "name": "DomainRoutingIsmFactory" }, { - "name": "StaticAggregationHook", - "address": "0xE5cA56294dA5Bd490D5Bc489B177B002ad16AF83" + "address": "0x67543F48DBe592F1A15FD9aE37b1AbD104c70cf3", + "name": "DomaingRoutingIsm" }, { - "name": "DomaingRoutingIsm", - "address": "0x7fFe8C9c17F46F94D784E148FbadD4bF66477722" + "address": "0xbe94Cd5d0973dB4A6E63Cb9e6b2b2042D99F9Bcd", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x33999AB153F68D481AAB1B238368Ffd1Fe81F360" + "address": "0x218C5DFb7C2bC1e051FCFebBa8C6D66Dda28617E", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0x3e6F45B03314bD21BcE4201666d483291575E391" + "address": "0x3dD1aB6B71EBfEd239869365FE5B8b47E0507d89", + "name": "StaticAggregationIsm" }, { - "name": "StaticAggregationIsm", - "address": "0x87935eB971eaA9826060261b07a919451dfd0409" + "address": "0x4BC580FF77Dd1Eb320E96D34FddC97F0F04D3feD", + "name": "StaticAggregationHook" }, { - "name": "StaticAggregationHook", - "address": "0xE5cA56294dA5Bd490D5Bc489B177B002ad16AF83" + "address": "0x67543F48DBe592F1A15FD9aE37b1AbD104c70cf3", + "name": "DomaingRoutingIsm" }, { - "name": "DomaingRoutingIsm", - "address": "0x7fFe8C9c17F46F94D784E148FbadD4bF66477722" + "address": "0xbe94Cd5d0973dB4A6E63Cb9e6b2b2042D99F9Bcd", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x33999AB153F68D481AAB1B238368Ffd1Fe81F360" + "address": "0x218C5DFb7C2bC1e051FCFebBa8C6D66Dda28617E", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0x3e6F45B03314bD21BcE4201666d483291575E391" + "address": "0x3dD1aB6B71EBfEd239869365FE5B8b47E0507d89", + "name": "StaticAggregationIsm" }, { - "name": "StaticAggregationIsm", - "address": "0x87935eB971eaA9826060261b07a919451dfd0409" + "address": "0x4BC580FF77Dd1Eb320E96D34FddC97F0F04D3feD", + "name": "StaticAggregationHook" }, { - "name": "StaticAggregationHook", - "address": "0xE5cA56294dA5Bd490D5Bc489B177B002ad16AF83" + "address": "0x67543F48DBe592F1A15FD9aE37b1AbD104c70cf3", + "name": "DomaingRoutingIsm" }, { - "name": "DomaingRoutingIsm", - "address": "0x7fFe8C9c17F46F94D784E148FbadD4bF66477722" + "address": "0xbe94Cd5d0973dB4A6E63Cb9e6b2b2042D99F9Bcd", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x33999AB153F68D481AAB1B238368Ffd1Fe81F360", - "constructorArguments": "", - "isProxy": true + "address": "0x218C5DFb7C2bC1e051FCFebBa8C6D66Dda28617E", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0x3e6F45B03314bD21BcE4201666d483291575E391", - "constructorArguments": "", - "isProxy": true + "address": "0x3dD1aB6B71EBfEd239869365FE5B8b47E0507d89", + "name": "StaticAggregationIsm" }, { - "name": "StaticAggregationIsm", - "address": "0x87935eB971eaA9826060261b07a919451dfd0409", - "constructorArguments": "", - "isProxy": true + "address": "0x4BC580FF77Dd1Eb320E96D34FddC97F0F04D3feD", + "name": "StaticAggregationHook" }, { - "name": "StaticAggregationHook", - "address": "0xE5cA56294dA5Bd490D5Bc489B177B002ad16AF83", - "constructorArguments": "", - "isProxy": true + "address": "0x67543F48DBe592F1A15FD9aE37b1AbD104c70cf3", + "name": "DomaingRoutingIsm" }, { - "name": "DomainRoutingIsmFactory", - "address": "0x17866ebE0e503784a9461d3e753dEeD0d3F61153", - "constructorArguments": "", - "isProxy": false + "address": "0xbe94Cd5d0973dB4A6E63Cb9e6b2b2042D99F9Bcd", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "DomaingRoutingIsm", - "address": "0xea80345322520d37770dbDeD3FE9c53ba93E70D8", - "constructorArguments": "", - "isProxy": true - } - ], - "plumetestnet": [ - { - "name": "StaticMerkleRootMultisigIsmFactory", - "address": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", - "constructorArguments": "", - "isProxy": false + "address": "0x218C5DFb7C2bC1e051FCFebBa8C6D66Dda28617E", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x87935eB971eaA9826060261b07a919451dfd0409", - "constructorArguments": "", - "isProxy": true + "address": "0x3dD1aB6B71EBfEd239869365FE5B8b47E0507d89", + "name": "StaticAggregationIsm" }, { - "name": "StaticMessageIdMultisigIsmFactory", - "address": "0x44b764045BfDC68517e10e783E69B376cef196B2", - "constructorArguments": "", - "isProxy": false + "address": "0x4BC580FF77Dd1Eb320E96D34FddC97F0F04D3feD", + "name": "StaticAggregationHook" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0xE5cA56294dA5Bd490D5Bc489B177B002ad16AF83", - "constructorArguments": "", - "isProxy": true + "address": "0x67543F48DBe592F1A15FD9aE37b1AbD104c70cf3", + "name": "DomaingRoutingIsm" }, { - "name": "StaticAggregationIsmFactory", - "address": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", + "address": "0xbe94Cd5d0973dB4A6E63Cb9e6b2b2042D99F9Bcd", "constructorArguments": "", - "isProxy": false + "isProxy": true, + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticAggregationIsm", - "address": "0x7fFe8C9c17F46F94D784E148FbadD4bF66477722", + "address": "0x218C5DFb7C2bC1e051FCFebBa8C6D66Dda28617E", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationHookFactory", - "address": "0x6966b0E55883d49BFB24539356a2f8A673E02039", + "address": "0x3dD1aB6B71EBfEd239869365FE5B8b47E0507d89", "constructorArguments": "", - "isProxy": false + "isProxy": true, + "name": "StaticAggregationIsm" }, { - "name": "StaticAggregationHook", - "address": "0x4863236F3a05A1A1F0850fF8cd09afeBAE82d953", + "address": "0x4BC580FF77Dd1Eb320E96D34FddC97F0F04D3feD", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "StaticAggregationHook" }, { - "name": "DomainRoutingIsmFactory", - "address": "0x54148470292C24345fb828B003461a9444414517", + "address": "0x3F100cBBE5FD5466BdB4B3a15Ac226957e7965Ad", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "DomainRoutingIsmFactory" }, { - "name": "DomaingRoutingIsm", - "address": "0x10c9FF6EEE4BaD29734322467f541C84001422C2", + "address": "0x9b0CC3BD9030CE269EF3124Bb36Cf954a490688e", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "DomaingRoutingIsm" } ] } diff --git a/typescript/infra/config/environments/testnet4/middleware.ts b/typescript/infra/config/environments/testnet4/middleware.ts index 607b4a3f6..cf3d2782c 100644 --- a/typescript/infra/config/environments/testnet4/middleware.ts +++ b/typescript/infra/config/environments/testnet4/middleware.ts @@ -1,5 +1,3 @@ -import { RpcConsensusType } from '@hyperlane-xyz/sdk'; - import { LiquidityLayerRelayerConfig } from '../../../src/config/middleware.js'; import { environment } from './chains.js'; @@ -12,5 +10,4 @@ export const liquidityLayerRelayerConfig: LiquidityLayerRelayerConfig = { namespace: environment, prometheusPushGateway: 'http://prometheus-prometheus-pushgateway.monitoring.svc.cluster.local:9091', - connectionType: RpcConsensusType.Single, }; diff --git a/typescript/infra/config/environments/testnet4/supportedChainNames.ts b/typescript/infra/config/environments/testnet4/supportedChainNames.ts index c65393e55..c966447a1 100644 --- a/typescript/infra/config/environments/testnet4/supportedChainNames.ts +++ b/typescript/infra/config/environments/testnet4/supportedChainNames.ts @@ -4,6 +4,7 @@ export const supportedChainNames = [ 'alfajores', 'bsctestnet', 'eclipsetestnet', + 'holesky', 'fuji', 'plumetestnet', 'scrollsepolia', diff --git a/typescript/infra/config/environments/testnet4/validators.ts b/typescript/infra/config/environments/testnet4/validators.ts index 6e594ffa7..965f0f38b 100644 --- a/typescript/infra/config/environments/testnet4/validators.ts +++ b/typescript/infra/config/environments/testnet4/validators.ts @@ -70,6 +70,21 @@ export const validatorChainConfig = ( 'bsctestnet', ), }, + holesky: { + interval: 13, + reorgPeriod: getReorgPeriod('holesky'), + validators: validatorsConfig( + { + [Contexts.Hyperlane]: ['0x7ab28ad88bb45867137ea823af88e2cb02359c03'], + [Contexts.ReleaseCandidate]: [ + '0x7ab28ad88bb45867137ea823af88e2cb02359c03', + ], + [Contexts.Neutron]: [], + }, + 'holesky', + ), + }, + scrollsepolia: { interval: 5, reorgPeriod: getReorgPeriod('scrollsepolia'), diff --git a/typescript/infra/config/registry.ts b/typescript/infra/config/registry.ts index a85488014..8c8be46bb 100644 --- a/typescript/infra/config/registry.ts +++ b/typescript/infra/config/registry.ts @@ -1,7 +1,11 @@ import { dirname, join } from 'path'; import { fileURLToPath } from 'url'; -import { ChainAddresses } from '@hyperlane-xyz/registry'; +import { + ChainAddresses, + MergedRegistry, + PartialRegistry, +} from '@hyperlane-xyz/registry'; import { FileSystemRegistry } from '@hyperlane-xyz/registry/fs'; import { ChainMap, @@ -35,10 +39,18 @@ export function setRegistry(reg: FileSystemRegistry) { registry = reg; } +/** + * Gets a FileSystemRegistry whose contents are found at the environment + * variable `REGISTRY_URI`, or `DEFAULT_REGISTRY_URI` if no env var is specified. + * This registry will not have any environment-specific overrides applied, + * and is useful for synchronous registry operations that do not require + * any overrides. + * @returns A FileSystemRegistry. + */ export function getRegistry(): FileSystemRegistry { if (!registry) { const registryUri = process.env.REGISTRY_URI || DEFAULT_REGISTRY_URI; - rootLogger.info('Using registry URI:', registryUri); + rootLogger.info({ registryUri }, 'Using registry URI'); registry = new FileSystemRegistry({ uri: registryUri, logger: rootLogger.child({ module: 'infra-registry' }), @@ -111,3 +123,26 @@ export function getMainnetAddresses(): ChainMap { export function getTestnetAddresses(): ChainMap { return getEnvAddresses('testnet4'); } + +/** + * Gets a registry, applying the provided overrides. The base registry + * that the overrides are applied to is the registry returned by `getRegistry`. + * @param chainMetadataOverrides Chain metadata overrides. + * @param chainAddressesOverrides Chain address overrides. + * @returns A MergedRegistry merging the registry from `getRegistry` and the overrides. + */ +export function getRegistryWithOverrides( + chainMetadataOverrides: ChainMap> = {}, + chainAddressesOverrides: ChainMap> = {}, +): MergedRegistry { + const baseRegistry = getRegistry(); + + const overrideRegistry = new PartialRegistry({ + chainMetadata: chainMetadataOverrides, + chainAddresses: chainAddressesOverrides, + }); + + return new MergedRegistry({ + registries: [baseRegistry, overrideRegistry], + }); +} diff --git a/typescript/infra/helm/helloworld-kathy/templates/_helpers.tpl b/typescript/infra/helm/helloworld-kathy/templates/_helpers.tpl index 12b1e0db9..286fd126a 100644 --- a/typescript/infra/helm/helloworld-kathy/templates/_helpers.tpl +++ b/typescript/infra/helm/helloworld-kathy/templates/_helpers.tpl @@ -91,10 +91,6 @@ The helloworld-kathy container {{- if .Values.hyperlane.cycleOnce }} - --cycle-once {{- end }} -{{- if .Values.hyperlane.connectionType }} - - --connection-type - - {{ .Values.hyperlane.connectionType }} -{{- end }} {{- if .Values.hyperlane.cyclesBetweenEthereumMessages }} - --cycles-between-ethereum-messages - "{{ .Values.hyperlane.cyclesBetweenEthereumMessages }}" diff --git a/typescript/infra/helm/helloworld-kathy/templates/external-secret.yaml b/typescript/infra/helm/helloworld-kathy/templates/external-secret.yaml index f0870da0a..115e249cd 100644 --- a/typescript/infra/helm/helloworld-kathy/templates/external-secret.yaml +++ b/typescript/infra/helm/helloworld-kathy/templates/external-secret.yaml @@ -33,7 +33,6 @@ spec: */}} {{- range .Values.hyperlane.chains }} GCP_SECRET_OVERRIDE_{{ $.Values.hyperlane.runEnv | upper }}_RPC_ENDPOINTS_{{ . | upper }}: {{ printf "'{{ .%s_rpcs | toString }}'" . }} - GCP_SECRET_OVERRIDE_{{ $.Values.hyperlane.runEnv | upper }}_RPC_ENDPOINT_{{ . | upper }}: {{ printf "'{{ .%s_rpc | toString }}'" . }} {{- end }} {{- if .Values.hyperlane.aws }} AWS_ACCESS_KEY_ID: {{ print "'{{ .aws_access_key_id | toString }}'" }} @@ -51,9 +50,6 @@ spec: - secretKey: {{ printf "%s_rpcs" . }} remoteRef: key: {{ printf "%s-rpc-endpoints-%s" $.Values.hyperlane.runEnv . }} - - secretKey: {{ printf "%s_rpc" . }} - remoteRef: - key: {{ printf "%s-rpc-endpoint-%s" $.Values.hyperlane.runEnv . }} {{- end }} {{- if .Values.hyperlane.aws }} - secretKey: aws_access_key_id diff --git a/typescript/infra/helm/key-funder/templates/cron-job.yaml b/typescript/infra/helm/key-funder/templates/cron-job.yaml index 610ebf8bb..b89122281 100644 --- a/typescript/infra/helm/key-funder/templates/cron-job.yaml +++ b/typescript/infra/helm/key-funder/templates/cron-job.yaml @@ -29,10 +29,6 @@ spec: - --contexts-and-roles - {{ $context }}={{ join "," $roles }} {{- end }} -{{- if .Values.hyperlane.connectionType }} - - --connection-type - - {{ .Values.hyperlane.connectionType }} -{{- end }} {{- range $chain, $balance := .Values.hyperlane.desiredBalancePerChain }} - --desired-balance-per-chain - {{ $chain }}={{ $balance }} diff --git a/typescript/infra/helm/key-funder/templates/env-var-external-secret.yaml b/typescript/infra/helm/key-funder/templates/env-var-external-secret.yaml index de6577bfa..2a95d627e 100644 --- a/typescript/infra/helm/key-funder/templates/env-var-external-secret.yaml +++ b/typescript/infra/helm/key-funder/templates/env-var-external-secret.yaml @@ -29,7 +29,6 @@ spec: */}} {{- range .Values.hyperlane.chains }} GCP_SECRET_OVERRIDE_{{ $.Values.hyperlane.runEnv | upper }}_RPC_ENDPOINTS_{{ . | upper }}: {{ printf "'{{ .%s_rpcs | toString }}'" . }} - GCP_SECRET_OVERRIDE_{{ $.Values.hyperlane.runEnv | upper }}_RPC_ENDPOINT_{{ . | upper }}: {{ printf "'{{ .%s_rpc | toString }}'" . }} {{- end }} data: - secretKey: deployer_key @@ -43,7 +42,4 @@ spec: - secretKey: {{ printf "%s_rpcs" . }} remoteRef: key: {{ printf "%s-rpc-endpoints-%s" $.Values.hyperlane.runEnv . }} - - secretKey: {{ printf "%s_rpc" . }} - remoteRef: - key: {{ printf "%s-rpc-endpoint-%s" $.Values.hyperlane.runEnv . }} {{- end }} diff --git a/typescript/infra/helm/liquidity-layer-relayers/templates/circle-relayer-deployment.yaml b/typescript/infra/helm/liquidity-layer-relayers/templates/circle-relayer-deployment.yaml index 02dc71378..a41589924 100644 --- a/typescript/infra/helm/liquidity-layer-relayers/templates/circle-relayer-deployment.yaml +++ b/typescript/infra/helm/liquidity-layer-relayers/templates/circle-relayer-deployment.yaml @@ -21,10 +21,6 @@ spec: - ./typescript/infra/scripts/middleware/circle-relayer.ts - -e - {{ .Values.hyperlane.runEnv }} -{{- if .Values.hyperlane.connectionType }} - - --connection-type - - {{ .Values.hyperlane.connectionType }} -{{- end }} envFrom: - secretRef: name: liquidity-layer-env-var-secret diff --git a/typescript/infra/helm/liquidity-layer-relayers/templates/env-var-external-secret.yaml b/typescript/infra/helm/liquidity-layer-relayers/templates/env-var-external-secret.yaml index a8d44b48c..62b311712 100644 --- a/typescript/infra/helm/liquidity-layer-relayers/templates/env-var-external-secret.yaml +++ b/typescript/infra/helm/liquidity-layer-relayers/templates/env-var-external-secret.yaml @@ -29,7 +29,6 @@ spec: */}} {{- range .Values.hyperlane.chains }} GCP_SECRET_OVERRIDE_{{ $.Values.hyperlane.runEnv | upper }}_RPC_ENDPOINTS_{{ . | upper }}: {{ printf "'{{ .%s_rpcs | toString }}'" . }} - GCP_SECRET_OVERRIDE_{{ $.Values.hyperlane.runEnv | upper }}_RPC_ENDPOINT_{{ . | upper }}: {{ printf "'{{ .%s_rpc | toString }}'" . }} {{- end }} data: - secretKey: deployer_key @@ -43,7 +42,4 @@ spec: - secretKey: {{ printf "%s_rpcs" . }} remoteRef: key: {{ printf "%s-rpc-endpoints-%s" $.Values.hyperlane.runEnv . }} - - secretKey: {{ printf "%s_rpc" . }} - remoteRef: - key: {{ printf "%s-rpc-endpoint-%s" $.Values.hyperlane.runEnv . }} {{- end }} diff --git a/typescript/infra/helm/liquidity-layer-relayers/templates/portal-relayer-deployment.yaml b/typescript/infra/helm/liquidity-layer-relayers/templates/portal-relayer-deployment.yaml index 8f1828424..933210d8d 100644 --- a/typescript/infra/helm/liquidity-layer-relayers/templates/portal-relayer-deployment.yaml +++ b/typescript/infra/helm/liquidity-layer-relayers/templates/portal-relayer-deployment.yaml @@ -21,10 +21,6 @@ spec: - ./typescript/infra/scripts/middleware/portal-relayer.ts - -e - {{ .Values.hyperlane.runEnv }} -{{- if .Values.hyperlane.connectionType }} - - --connection-type - - {{ .Values.hyperlane.connectionType }} -{{- end }} envFrom: - secretRef: name: liquidity-layer-env-var-secret diff --git a/typescript/infra/package.json b/typescript/infra/package.json index 4f8fcb014..67de934c2 100644 --- a/typescript/infra/package.json +++ b/typescript/infra/package.json @@ -1,7 +1,7 @@ { "name": "@hyperlane-xyz/infra", "description": "Infrastructure utilities for the Hyperlane Network", - "version": "3.12.2", + "version": "3.13.0", "dependencies": { "@arbitrum/sdk": "^3.0.0", "@aws-sdk/client-iam": "^3.74.0", @@ -12,10 +12,11 @@ "@ethersproject/experimental": "^5.7.0", "@ethersproject/hardware-wallets": "^5.7.0", "@ethersproject/providers": "^5.7.2", - "@hyperlane-xyz/helloworld": "3.12.2", + "@google-cloud/secret-manager": "^5.5.0", + "@hyperlane-xyz/helloworld": "3.13.0", "@hyperlane-xyz/registry": "1.3.0", - "@hyperlane-xyz/sdk": "3.12.2", - "@hyperlane-xyz/utils": "3.12.2", + "@hyperlane-xyz/sdk": "3.13.0", + "@hyperlane-xyz/utils": "3.13.0", "@nomiclabs/hardhat-etherscan": "^3.0.3", "@solana/web3.js": "^1.78.0", "asn1.js": "5.4.1", diff --git a/typescript/infra/scripts/agent-utils.ts b/typescript/infra/scripts/agent-utils.ts index d027d5aad..313af5d44 100644 --- a/typescript/infra/scripts/agent-utils.ts +++ b/typescript/infra/scripts/agent-utils.ts @@ -1,14 +1,14 @@ import path, { join } from 'path'; import yargs, { Argv } from 'yargs'; -import { ChainAddresses } from '@hyperlane-xyz/registry'; +import { ChainAddresses, IRegistry } from '@hyperlane-xyz/registry'; import { ChainMap, ChainMetadata, ChainName, CoreConfig, + MultiProtocolProvider, MultiProvider, - RpcConsensusType, collectValidators, } from '@hyperlane-xyz/sdk'; import { @@ -16,6 +16,7 @@ import { ProtocolType, objFilter, objMap, + objMerge, promiseObjAll, rootLogger, symmetricDifference, @@ -35,7 +36,10 @@ import { getCurrentKubernetesContext } from '../src/agents/index.js'; import { getCloudAgentKey } from '../src/agents/key-utils.js'; import { CloudAgentKey } from '../src/agents/keys.js'; import { RootAgentConfig } from '../src/config/agent/agent.js'; -import { fetchProvider } from '../src/config/chain.js'; +import { + fetchProvider, + getSecretMetadataOverrides, +} from '../src/config/chain.js'; import { AgentEnvironment, DeployEnvironment, @@ -47,6 +51,7 @@ import { assertContext, assertRole, getInfraPath, + inCIMode, readJSONAtPath, writeMergedJSONAtPath, } from '../src/utils/utils.js'; @@ -96,13 +101,6 @@ export function withModuleAndFork(args: Argv) { .alias('f', 'fork'); } -export function withNetwork(args: Argv) { - return args - .describe('network', 'network to target') - .choices('network', getChains()) - .alias('n', 'network'); -} - export function withContext(args: Argv) { return args .describe('context', 'deploy context') @@ -112,6 +110,17 @@ export function withContext(args: Argv) { .demandOption('context'); } +export function withChainRequired(args: Argv) { + return withChain(args).demandOption('chain'); +} + +export function withChain(args: Argv) { + return args + .describe('chain', 'chain name') + .choices('chain', getChains()) + .alias('c', 'chain'); +} + export function withProtocol(args: Argv) { return args .describe('protocol', 'protocol type') @@ -171,6 +180,17 @@ export function withConcurrentDeploy(args: Argv) { .default('concurrentDeploy', false); } +export function withRpcUrls(args: Argv) { + return args + .describe( + 'rpcUrls', + 'rpc urls in a comma separated list, in order of preference', + ) + .string('rpcUrls') + .demandOption('rpcUrls') + .alias('r', 'rpcUrls'); +} + // not requiring to build coreConfig to get agentConfig export async function getAgentConfigsBasedOnArgs(argv?: { environment: DeployEnvironment; @@ -283,8 +303,8 @@ export function ensureValidatorConfigConsistency(agentConfig: RootAgentConfig) { export function getKeyForRole( environment: DeployEnvironment, context: Contexts, - chain: ChainName, role: Role, + chain?: ChainName, index?: number, ): CloudAgentKey { debugLog(`Getting key for ${role} role`); @@ -292,33 +312,32 @@ export function getKeyForRole( return getCloudAgentKey(agentConfig, role, chain, index); } +export async function getMultiProtocolProvider( + registry: IRegistry, +): Promise { + const chainMetadata = await registry.getMetadata(); + return new MultiProtocolProvider(chainMetadata); +} + export async function getMultiProviderForRole( - txConfigs: ChainMap, environment: DeployEnvironment, + registry: IRegistry, context: Contexts, role: Role, index?: number, - // TODO: rename to consensusType? - connectionType?: RpcConsensusType, ): Promise { + const chainMetadata = await registry.getMetadata(); debugLog(`Getting multiprovider for ${role} role`); - const multiProvider = new MultiProvider(txConfigs); - if (process.env.CI === 'true') { - debugLog('Returning multiprovider with default RPCs in CI'); - // Return the multiProvider with default RPCs + const multiProvider = new MultiProvider(chainMetadata); + if (inCIMode()) { + debugLog('Running in CI, returning multiprovider without secret keys'); return multiProvider; } await promiseObjAll( - objMap(txConfigs, async (chain, _) => { + objMap(chainMetadata, async (chain, _) => { if (multiProvider.getProtocol(chain) === ProtocolType.Ethereum) { - const provider = await fetchProvider( - environment, - chain, - connectionType, - ); - const key = getKeyForRole(environment, context, chain, role, index); - const signer = await key.getSigner(provider); - multiProvider.setProvider(chain, provider); + const key = getKeyForRole(environment, context, role, chain, index); + const signer = await key.getSigner(); multiProvider.setSigner(chain, signer); } }), @@ -330,23 +349,22 @@ export async function getMultiProviderForRole( // Note: this will only work for keystores that allow key's to be extracted. // I.e. GCP will work but AWS HSMs will not. export async function getKeysForRole( - txConfigs: ChainMap, environment: DeployEnvironment, + supportedChainNames: ChainName[], context: Contexts, role: Role, index?: number, ): Promise> { - if (process.env.CI === 'true') { + if (inCIMode()) { debugLog('No keys to return in CI'); return {}; } - const keys = await promiseObjAll( - objMap(txConfigs, async (chain, _) => - getKeyForRole(environment, context, chain, role, index), - ), - ); - return keys; + const keyEntries = supportedChainNames.map((chain) => [ + chain, + getKeyForRole(environment, context, role, chain, index), + ]); + return Object.fromEntries(keyEntries); } export function getEnvironmentDirectory(environment: DeployEnvironment) { diff --git a/typescript/infra/scripts/agents/update-agent-config.ts b/typescript/infra/scripts/agents/update-agent-config.ts index 2ec6431b8..6b0ed65fd 100644 --- a/typescript/infra/scripts/agents/update-agent-config.ts +++ b/typescript/infra/scripts/agents/update-agent-config.ts @@ -6,7 +6,12 @@ async function main() { const { environment } = await getArgs().argv; const envConfig = getEnvironmentConfig(environment); - let multiProvider = await envConfig.getMultiProvider(); + let multiProvider = await envConfig.getMultiProvider( + undefined, + undefined, + // Don't use secrets + false, + ); await writeAgentConfig(multiProvider, environment); } diff --git a/typescript/infra/scripts/check-rpc-urls.ts b/typescript/infra/scripts/check-rpc-urls.ts index 2ab70835b..307e4515c 100644 --- a/typescript/infra/scripts/check-rpc-urls.ts +++ b/typescript/infra/scripts/check-rpc-urls.ts @@ -2,25 +2,20 @@ import { ethers } from 'ethers'; import { rootLogger } from '@hyperlane-xyz/utils'; -import { getSecretRpcEndpoint } from '../src/agents/index.js'; +import { getSecretRpcEndpoints } from '../src/agents/index.js'; import { getArgs } from './agent-utils.js'; import { getEnvironmentConfig } from './core-utils.js'; -// TODO remove this script as part of migration to CLI -// It's redundant with metadata-check.ts in the SDK async function main() { const { environment } = await getArgs().argv; - const config = await getEnvironmentConfig(environment); + const config = getEnvironmentConfig(environment); const multiProvider = await config.getMultiProvider(); const chains = multiProvider.getKnownChainNames(); const providers: [string, ethers.providers.JsonRpcProvider][] = []; for (const chain of chains) { rootLogger.debug(`Building providers for ${chain}`); - const rpcData = [ - ...(await getSecretRpcEndpoint(environment, chain, false)), - ...(await getSecretRpcEndpoint(environment, chain, true)), - ]; + const rpcData = await getSecretRpcEndpoints(environment, chain); for (const url of rpcData) providers.push([chain, new ethers.providers.StaticJsonRpcProvider(url)]); } diff --git a/typescript/infra/scripts/deploy.ts b/typescript/infra/scripts/deploy.ts index 733bfd819..fc6719051 100644 --- a/typescript/infra/scripts/deploy.ts +++ b/typescript/infra/scripts/deploy.ts @@ -42,10 +42,10 @@ import { getArgs, getModuleDirectory, withBuildArtifactPath, + withChain, withConcurrentDeploy, withContext, withModuleAndFork, - withNetwork, } from './agent-utils.js'; import { getEnvironmentConfig, getHyperlaneCore } from './core-utils.js'; @@ -55,12 +55,12 @@ async function main() { module, fork, environment, - network, + chain, buildArtifactPath, concurrentDeploy, } = await withContext( withConcurrentDeploy( - withNetwork(withModuleAndFork(withBuildArtifactPath(getArgs()))), + withChain(withModuleAndFork(withBuildArtifactPath(getArgs()))), ), ).argv; const envConfig = getEnvironmentConfig(environment); @@ -233,7 +233,7 @@ async function main() { // prompt for confirmation in production environments if (environment !== 'test' && !fork) { - const confirmConfig = network ? config[network] : config; + const confirmConfig = chain ? config[chain] : config; console.log(JSON.stringify(confirmConfig, null, 2)); const { value: confirmed } = await prompts({ type: 'confirm', @@ -250,7 +250,7 @@ async function main() { config, deployer, cache, - network ?? fork, + chain ?? fork, agentConfig, ); } diff --git a/typescript/infra/scripts/funding/fund-keys-from-deployer.ts b/typescript/infra/scripts/funding/fund-keys-from-deployer.ts index 02b15a978..b949b9a32 100644 --- a/typescript/infra/scripts/funding/fund-keys-from-deployer.ts +++ b/typescript/infra/scripts/funding/fund-keys-from-deployer.ts @@ -9,7 +9,6 @@ import { ChainName, HyperlaneIgp, MultiProvider, - RpcConsensusType, } from '@hyperlane-xyz/sdk'; import { Address, objFilter, objMap, rootLogger } from '@hyperlane-xyz/utils'; @@ -177,11 +176,6 @@ async function main() { ) .coerce('desired-kathy-balance-per-chain', parseBalancePerChain) - .string('connection-type') - .describe('connection-type', 'The provider connection type to use for RPCs') - .default('connection-type', RpcConsensusType.Single) - .demandOption('connection-type') - .boolean('skip-igp-claim') .describe('skip-igp-claim', 'If true, never claims funds from the IGP') .default('skip-igp-claim', false).argv; @@ -191,7 +185,6 @@ async function main() { const multiProvider = await config.getMultiProvider( Contexts.Hyperlane, // Always fund from the hyperlane context Role.Deployer, // Always fund from the deployer - argv.connectionType, ); let contextFunders: ContextFunder[]; @@ -381,7 +374,7 @@ class ContextFunder { [Role.Kathy]: '', }; const roleKeysPerChain: ChainMap> = {}; - const chains = getEnvironmentConfig(environment).chainMetadataConfigs; + const { supportedChainNames } = getEnvironmentConfig(environment); for (const role of rolesToFund) { assertFundableRole(role); // only the relayer and kathy are fundable keys const roleAddress = fetchLocalKeyAddresses(role)[environment][context]; @@ -392,7 +385,7 @@ class ContextFunder { } fundableRoleKeys[role] = roleAddress; - for (const chain of Object.keys(chains)) { + for (const chain of supportedChainNames) { if (!roleKeysPerChain[chain as ChainName]) { roleKeysPerChain[chain as ChainName] = { [Role.Relayer]: [], diff --git a/typescript/infra/scripts/helloworld/kathy.ts b/typescript/infra/scripts/helloworld/kathy.ts index 101bd6779..f3cbb1a49 100644 --- a/typescript/infra/scripts/helloworld/kathy.ts +++ b/typescript/infra/scripts/helloworld/kathy.ts @@ -11,7 +11,6 @@ import { MultiProtocolCore, MultiProvider, ProviderType, - RpcConsensusType, TypedTransactionReceipt, resolveOrDeployAccountOwner, } from '@hyperlane-xyz/sdk'; @@ -20,6 +19,7 @@ import { ProtocolType, ensure0x, objMap, + pick, retryAsync, rootLogger, sleep, @@ -28,7 +28,6 @@ import { } from '@hyperlane-xyz/utils'; import { Contexts } from '../../config/contexts.js'; -import { testnetConfigs } from '../../config/environments/testnet4/chains.js'; import { hyperlaneHelloworld, releaseCandidateHelloworld, @@ -128,16 +127,6 @@ function getKathyArgs() { chainStrs.map((chainStr: string) => assertChain(chainStr)), ) - .string('connection-type') - .describe('connection-type', 'The provider connection type to use for RPCs') - .default('connection-type', RpcConsensusType.Single) - .choices('connection-type', [ - RpcConsensusType.Single, - RpcConsensusType.Quorum, - RpcConsensusType.Fallback, - ]) - .demandOption('connection-type') - .number('cycles-between-ethereum-messages') .describe( 'cycles-between-ethereum-messages', @@ -156,7 +145,6 @@ async function main(): Promise { fullCycleTime, messageSendTimeout, messageReceiptTimeout, - connectionType, cyclesBetweenEthereumMessages, } = await getKathyArgs(); @@ -166,7 +154,6 @@ async function main(): Promise { logger.debug('Starting up', { environment }); const coreConfig = getEnvironmentConfig(environment); - // const coreConfig = getCoreConfigStub(environment); const { app, core, igp, multiProvider, keys } = await getHelloWorldMultiProtocolApp( @@ -174,7 +161,6 @@ async function main(): Promise { context, Role.Kathy, undefined, - connectionType, ); const appChains = app.chains(); @@ -550,7 +536,14 @@ async function updateWalletBalanceMetricFor( } // Get a core config intended for testing Kathy without secret access -export function getCoreConfigStub(environment: DeployEnvironment) { +export async function getCoreConfigStub(environment: DeployEnvironment) { + const environmentConfig = getEnvironmentConfig(environment); + // Don't fetch any secrets. + const registry = await environmentConfig.getRegistry(false); + const testnetConfigs = pick( + await registry.getMetadata(), + environmentConfig.supportedChainNames, + ); const multiProvider = new MultiProvider({ // Desired chains here. Key must have funds on these chains ...testnetConfigs, diff --git a/typescript/infra/scripts/helloworld/utils.ts b/typescript/infra/scripts/helloworld/utils.ts index 38ab8969f..03cb408eb 100644 --- a/typescript/infra/scripts/helloworld/utils.ts +++ b/typescript/infra/scripts/helloworld/utils.ts @@ -8,7 +8,6 @@ import { MultiProtocolCore, MultiProtocolProvider, MultiProvider, - RpcConsensusType, attachContractsMap, attachContractsMapAndGetForeignDeployments, filterChainMapToProtocol, @@ -28,12 +27,10 @@ export async function getHelloWorldApp( context: Contexts, keyRole: Role, keyContext: Contexts = context, - connectionType: RpcConsensusType = RpcConsensusType.Single, ) { const multiProvider: MultiProvider = await coreConfig.getMultiProvider( keyContext, keyRole, - connectionType, ); const helloworldConfig = getHelloWorldConfig(coreConfig, context); @@ -61,12 +58,10 @@ export async function getHelloWorldMultiProtocolApp( context: Contexts, keyRole: Role, keyContext: Contexts = context, - connectionType: RpcConsensusType = RpcConsensusType.Single, ) { const multiProvider: MultiProvider = await coreConfig.getMultiProvider( keyContext, keyRole, - connectionType, ); const envAddresses = getEnvAddresses(coreConfig.environment); diff --git a/typescript/infra/scripts/print-chain-metadatas.ts b/typescript/infra/scripts/print-chain-metadatas.ts index 8653ef908..ff8f771fe 100644 --- a/typescript/infra/scripts/print-chain-metadatas.ts +++ b/typescript/infra/scripts/print-chain-metadatas.ts @@ -1,3 +1,5 @@ +import { pick } from '@hyperlane-xyz/utils'; + import { getArgs } from './agent-utils.js'; import { getEnvironmentConfig } from './core-utils.js'; @@ -9,7 +11,15 @@ async function main() { const environmentConfig = getEnvironmentConfig(args.environment); - console.log(JSON.stringify(environmentConfig.chainMetadataConfigs, null, 2)); + // Intentionally do not include any secrets in the output + const registry = await environmentConfig.getRegistry(false); + const allMetadata = await registry.getMetadata(); + const environmentMetadata = pick( + allMetadata, + environmentConfig.supportedChainNames, + ); + + console.log(JSON.stringify(environmentMetadata, null, 2)); } main().catch((err) => { diff --git a/typescript/infra/scripts/print-gas-prices.ts b/typescript/infra/scripts/print-gas-prices.ts index e06f13421..61126c371 100644 --- a/typescript/infra/scripts/print-gas-prices.ts +++ b/typescript/infra/scripts/print-gas-prices.ts @@ -1,47 +1,67 @@ import { ethers } from 'ethers'; -import { MultiProtocolProvider, ProviderType } from '@hyperlane-xyz/sdk'; -import { objMap, promiseObjAll } from '@hyperlane-xyz/utils'; +import { + ChainMap, + MultiProtocolProvider, + ProviderType, +} from '@hyperlane-xyz/sdk'; -import { mainnetConfigs } from '../config/environments/mainnet3/chains.js'; -import { getCosmosChainGasPrice } from '../src/config/gas-oracle.js'; +import { + GasPriceConfig, + getCosmosChainGasPrice, +} from '../src/config/gas-oracle.js'; + +import { getEnvironmentConfig } from './core-utils.js'; async function main() { - const allMetadatas = mainnetConfigs; - - const mpp = new MultiProtocolProvider(allMetadatas); - - const prices = await promiseObjAll( - objMap(allMetadatas, async (chain, metadata) => { - const provider = mpp.getProvider(chain); - switch (provider.type) { - case ProviderType.EthersV5: { - const gasPrice = await provider.provider.getGasPrice(); - return { - amount: ethers.utils.formatUnits(gasPrice, 'gwei'), - decimals: 9, - }; - } - case ProviderType.CosmJsWasm: { - const { amount } = await getCosmosChainGasPrice(chain); - - return { - amount, - decimals: 1, - }; - } - case ProviderType.SolanaWeb3: - // TODO get a reasonable value - return '0.001'; - default: - throw new Error(`Unsupported provider type: ${provider.type}`); - } - }), + const environmentConfig = getEnvironmentConfig('mainnet3'); + + const mpp = await environmentConfig.getMultiProtocolProvider(); + + const prices: ChainMap = Object.fromEntries( + await Promise.all( + environmentConfig.supportedChainNames.map(async (chain) => [ + chain, + await getGasPrice(mpp, chain), + ]), + ), ); console.log(JSON.stringify(prices, null, 2)); } +async function getGasPrice( + mpp: MultiProtocolProvider, + chain: string, +): Promise { + const provider = mpp.getProvider(chain); + switch (provider.type) { + case ProviderType.EthersV5: { + const gasPrice = await provider.provider.getGasPrice(); + return { + amount: ethers.utils.formatUnits(gasPrice, 'gwei'), + decimals: 9, + }; + } + case ProviderType.CosmJsWasm: { + const { amount } = await getCosmosChainGasPrice(chain); + + return { + amount, + decimals: 1, + }; + } + case ProviderType.SolanaWeb3: + // TODO get a reasonable value + return { + amount: '0.001', + decimals: 9, + }; + default: + throw new Error(`Unsupported provider type: ${provider.type}`); + } +} + main() .then() .catch((err) => { diff --git a/typescript/infra/scripts/print-token-prices.ts b/typescript/infra/scripts/print-token-prices.ts index 35c30fb5b..b125bac95 100644 --- a/typescript/infra/scripts/print-token-prices.ts +++ b/typescript/infra/scripts/print-token-prices.ts @@ -1,11 +1,17 @@ -import { objMap } from '@hyperlane-xyz/utils'; +import { objMap, pick } from '@hyperlane-xyz/utils'; -import { mainnetConfigs } from '../config/environments/mainnet3/chains.js'; +import { getEnvironmentConfig } from './core-utils.js'; const CURRENCY = 'usd'; async function main() { - const metadata = mainnetConfigs; + const environmentConfig = getEnvironmentConfig('mainnet3'); + + const registry = await environmentConfig.getRegistry(); + const metadata = pick( + await registry.getMetadata(), + environmentConfig.supportedChainNames, + ); const ids = objMap( metadata, diff --git a/typescript/infra/scripts/secret-rpc-urls/get-rpc-urls.ts b/typescript/infra/scripts/secret-rpc-urls/get-rpc-urls.ts new file mode 100644 index 000000000..0f2c9fd5f --- /dev/null +++ b/typescript/infra/scripts/secret-rpc-urls/get-rpc-urls.ts @@ -0,0 +1,26 @@ +import { + getSecretRpcEndpoints, + secretRpcEndpointsExist, +} from '../../src/agents/index.js'; +import { getArgs, withChainRequired } from '../agent-utils.js'; + +async function main() { + const { environment, chain } = await withChainRequired(getArgs()).argv; + const secretExists = await secretRpcEndpointsExist(environment, chain); + if (!secretExists) { + console.log( + `No secret rpc urls found for ${chain} in ${environment} environment`, + ); + process.exit(0); + } + + const secrets = await getSecretRpcEndpoints(environment, chain); + console.log(secrets); +} + +main() + .then() + .catch((e) => { + console.error(e); + process.exit(1); + }); diff --git a/typescript/infra/scripts/secret-rpc-urls/set-rpc-urls.ts b/typescript/infra/scripts/secret-rpc-urls/set-rpc-urls.ts new file mode 100644 index 000000000..78881a01e --- /dev/null +++ b/typescript/infra/scripts/secret-rpc-urls/set-rpc-urls.ts @@ -0,0 +1,120 @@ +import { confirm } from '@inquirer/prompts'; +import { ethers } from 'ethers'; + +import { + getSecretRpcEndpoints, + getSecretRpcEndpointsLatestVersionName, + secretRpcEndpointsExist, + setSecretRpcEndpoints, +} from '../../src/agents/index.js'; +import { disableGCPSecretVersion } from '../../src/utils/gcloud.js'; +import { isEthereumProtocolChain } from '../../src/utils/utils.js'; +import { getArgs, withChainRequired, withRpcUrls } from '../agent-utils.js'; + +async function testProviders(rpcUrlsArray: string[]): Promise { + let providersSucceeded = true; + for (const url of rpcUrlsArray) { + const provider = new ethers.providers.StaticJsonRpcProvider(url); + try { + const blockNumber = await provider.getBlockNumber(); + console.log(`Valid provider for ${url} with block number ${blockNumber}`); + } catch (e) { + console.error(`Provider failed: ${url}`); + providersSucceeded = false; + } + } + + return providersSucceeded; +} + +async function main() { + const { environment, chain, rpcUrls } = await withRpcUrls( + withChainRequired(getArgs()), + ).argv; + + const rpcUrlsArray = rpcUrls + .split(/,\s*/) + .filter(Boolean) // filter out empty strings + .map((url) => url.trim()); + + if (!rpcUrlsArray.length) { + console.error('No rpc urls provided, Exiting.'); + process.exit(1); + } + + const secretPayload = JSON.stringify(rpcUrlsArray); + + const secretExists = await secretRpcEndpointsExist(environment, chain); + if (!secretExists) { + console.log( + `No secret rpc urls found for ${chain} in ${environment} environment\n`, + ); + } else { + const currentSecrets = await getSecretRpcEndpoints(environment, chain); + console.log( + `Current secrets found for ${chain} in ${environment} environment:\n${JSON.stringify( + currentSecrets, + null, + 2, + )}\n`, + ); + } + + const confirmedSet = await confirm({ + message: `Are you sure you want to set the following RPC URLs for ${chain} in ${environment}?\n${secretPayload}\n`, + }); + + if (!confirmedSet) { + console.log('Exiting without setting secret.'); + process.exit(0); + } + + if (isEthereumProtocolChain(chain)) { + console.log('\nTesting providers...'); + const testPassed = await testProviders(rpcUrlsArray); + if (!testPassed) { + console.error('At least one provider failed. Exiting.'); + process.exit(1); + } + + const confirmedProviders = await confirm({ + message: `All providers passed. Do you want to continue setting the secret?\n`, + }); + + if (!confirmedProviders) { + console.log('Exiting without setting secret.'); + process.exit(0); + } + } else { + console.log( + 'Skipping provider testing as chain is not an Ethereum protocol chain.', + ); + } + + let latestVersionName; + if (secretExists) { + latestVersionName = await getSecretRpcEndpointsLatestVersionName( + environment, + chain, + ); + } + console.log(`Setting secret...`); + await setSecretRpcEndpoints(environment, chain, secretPayload); + console.log(`Added secret version!`); + + if (latestVersionName) { + try { + await disableGCPSecretVersion(latestVersionName); + console.log(`Disabled previous version of the secret!`); + } catch (e) { + console.log(`Could not disable previous version of the secret`); + } + } +} + +main() + .then() + .catch((e) => { + console.error(e); + process.exit(1); + }); diff --git a/typescript/infra/scripts/verify.ts b/typescript/infra/scripts/verify.ts index 9c15c6597..0e1f782d4 100644 --- a/typescript/infra/scripts/verify.ts +++ b/typescript/infra/scripts/verify.ts @@ -12,12 +12,12 @@ import { } from '../src/deployment/verify.js'; import { readJSONAtPath } from '../src/utils/utils.js'; -import { getArgs, withBuildArtifactPath, withNetwork } from './agent-utils.js'; +import { getArgs, withBuildArtifactPath, withChain } from './agent-utils.js'; import { getEnvironmentConfig } from './core-utils.js'; async function main() { - const { environment, buildArtifactPath, verificationArtifactPath, network } = - await withNetwork(withBuildArtifactPath(getArgs())) + const { environment, buildArtifactPath, verificationArtifactPath, chain } = + await withChain(withBuildArtifactPath(getArgs())) .string('verificationArtifactPath') .describe( 'verificationArtifactPath', @@ -54,7 +54,7 @@ async function main() { // verify all the things const failedResults = ( - await verifier.verify(network ? [network] : undefined) + await verifier.verify(chain ? [chain] : undefined) ).filter((result) => result.status === 'rejected'); // only log the failed verifications to console diff --git a/typescript/infra/src/agents/index.ts b/typescript/infra/src/agents/index.ts index 41eabefae..c959bca65 100644 --- a/typescript/infra/src/agents/index.ts +++ b/typescript/infra/src/agents/index.ts @@ -17,7 +17,12 @@ import { ScraperConfigHelper } from '../config/agent/scraper.js'; import { ValidatorConfigHelper } from '../config/agent/validator.js'; import { DeployEnvironment } from '../config/environment.js'; import { AgentRole, Role } from '../roles.js'; -import { fetchGCPSecret } from '../utils/gcloud.js'; +import { + fetchGCPSecret, + gcpSecretExistsUsingClient, + getGcpSecretLatestVersionName, + setGCPSecretUsingClient, +} from '../utils/gcloud.js'; import { HelmCommand, buildHelmChartDependencies, @@ -287,6 +292,13 @@ export class ValidatorHelmManager extends MultichainAgentHelmManager { } } +export function getSecretName( + environment: string, + chainName: ChainName, +): string { + return `${environment}-rpc-endpoints-${chainName}`; +} + export async function getSecretAwsCredentials(agentConfig: AgentContextConfig) { return { accessKeyId: await fetchGCPSecret( @@ -300,20 +312,14 @@ export async function getSecretAwsCredentials(agentConfig: AgentContextConfig) { }; } -export async function getSecretRpcEndpoint( +export async function getSecretRpcEndpoints( environment: string, chainName: ChainName, - quorum = false, ): Promise { - const secret = await fetchGCPSecret( - `${environment}-rpc-endpoint${quorum ? 's' : ''}-${chainName}`, - quorum, - ); - if (typeof secret != 'string' && !Array.isArray(secret)) { - throw Error(`Expected secret for ${chainName} rpc endpoint`); - } + const secret = await fetchGCPSecret(getSecretName(environment, chainName)); + if (!Array.isArray(secret)) { - return [secret.trimEnd()]; + throw Error(`Expected secret for ${chainName} rpc endpoint`); } return secret.map((i) => { @@ -323,6 +329,29 @@ export async function getSecretRpcEndpoint( }); } +export async function getSecretRpcEndpointsLatestVersionName( + environment: string, + chainName: ChainName, +) { + return getGcpSecretLatestVersionName(getSecretName(environment, chainName)); +} + +export async function secretRpcEndpointsExist( + environment: string, + chainName: ChainName, +): Promise { + return gcpSecretExistsUsingClient(getSecretName(environment, chainName)); +} + +export async function setSecretRpcEndpoints( + environment: string, + chainName: ChainName, + endpoints: string, +) { + const secretName = getSecretName(environment, chainName); + await setGCPSecretUsingClient(secretName, endpoints); +} + export async function getSecretDeployerKey( environment: DeployEnvironment, context: Contexts, diff --git a/typescript/infra/src/config/chain.ts b/typescript/infra/src/config/chain.ts index b64035b2b..51584fc37 100644 --- a/typescript/infra/src/config/chain.ts +++ b/typescript/infra/src/config/chain.ts @@ -1,16 +1,19 @@ +import { SecretManagerServiceClient } from '@google-cloud/secret-manager'; import { providers } from 'ethers'; +import { IRegistry } from '@hyperlane-xyz/registry'; import { + ChainMap, ChainMetadata, ChainName, HyperlaneSmartProvider, ProviderRetryOptions, - RpcConsensusType, } from '@hyperlane-xyz/sdk'; -import { ProtocolType, objFilter } from '@hyperlane-xyz/utils'; +import { ProtocolType, objFilter, objMerge } from '@hyperlane-xyz/utils'; -import { getChain } from '../../config/registry.js'; -import { getSecretRpcEndpoint } from '../agents/index.js'; +import { getChain, getRegistryWithOverrides } from '../../config/registry.js'; +import { getSecretRpcEndpoints } from '../agents/index.js'; +import { inCIMode } from '../utils/utils.js'; import { DeployEnvironment } from './environment.js'; @@ -20,37 +23,24 @@ export const defaultRetry: ProviderRetryOptions = { }; export async function fetchProvider( - environment: DeployEnvironment, chainName: ChainName, - connectionType: RpcConsensusType = RpcConsensusType.Single, ): Promise { const chainMetadata = getChain(chainName); if (!chainMetadata) { throw Error(`Unsupported chain: ${chainName}`); } const chainId = chainMetadata.chainId; - const single = connectionType === RpcConsensusType.Single; let rpcData = chainMetadata.rpcUrls.map((url) => url.http); if (rpcData.length === 0) { - rpcData = await getSecretRpcEndpoint(environment, chainName, !single); + throw Error(`No RPC URLs found for chain: ${chainName}`); } - if (connectionType === RpcConsensusType.Single) { - return HyperlaneSmartProvider.fromRpcUrl(chainId, rpcData[0], defaultRetry); - } else if ( - connectionType === RpcConsensusType.Quorum || - connectionType === RpcConsensusType.Fallback - ) { - return new HyperlaneSmartProvider( - chainId, - rpcData.map((url) => ({ http: url })), - undefined, - // disable retry for quorum - connectionType === RpcConsensusType.Fallback ? defaultRetry : undefined, - ); - } else { - throw Error(`Unsupported connectionType: ${connectionType}`); - } + return new HyperlaneSmartProvider( + chainId, + rpcData.map((url) => ({ http: url })), + undefined, + defaultRetry, + ); } export function getChainMetadatas(chains: Array) { @@ -73,3 +63,69 @@ export function getChainMetadatas(chains: Array) { return { ethereumMetadatas, nonEthereumMetadatas }; } + +/** + * Gets the registry for the given environment, with optional overrides and + * the ability to get overrides from secrets. + * @param deployEnv The deploy environment. + * @param chains The chains to get metadata for. + * @param defaultChainMetadataOverrides The default chain metadata overrides. If + * secret overrides are used, the secret overrides will be merged with these and + * take precedence. + * @param useSecrets Whether to fetch metadata overrides from secrets. + * @returns A registry with overrides for the given environment. + */ +export async function getRegistryForEnvironment( + deployEnv: DeployEnvironment, + chains: ChainName[], + defaultChainMetadataOverrides: ChainMap> = {}, + useSecrets: boolean = true, +): Promise { + let overrides = defaultChainMetadataOverrides; + if (useSecrets && !inCIMode()) { + overrides = objMerge( + overrides, + await getSecretMetadataOverrides(deployEnv, chains), + ); + } + const registry = getRegistryWithOverrides(overrides); + return registry; +} + +/** + * Gets chain metadata overrides from GCP secrets. + * @param deployEnv The deploy environment. + * @param chains The chains to get metadata overrides for. + * @returns A partial chain metadata map with the secret overrides. + */ +export async function getSecretMetadataOverrides( + deployEnv: DeployEnvironment, + chains: string[], +): Promise>> { + const chainMetadataOverrides: ChainMap> = {}; + + const secretRpcUrls = await Promise.all( + chains.map(async (chain) => { + const rpcUrls = await getSecretRpcEndpoints(deployEnv, chain); + return { + chain, + rpcUrls, + }; + }), + ); + + for (const { chain, rpcUrls } of secretRpcUrls) { + if (rpcUrls.length === 0) { + throw Error(`No secret RPC URLs found for chain: ${chain}`); + } + // Need explicit casting here because Zod expects a non-empty array. + const metadataRpcUrls = rpcUrls.map((rpcUrl: string) => ({ + http: rpcUrl, + })) as ChainMetadata['rpcUrls']; + chainMetadataOverrides[chain] = { + rpcUrls: metadataRpcUrls, + }; + } + + return chainMetadataOverrides; +} diff --git a/typescript/infra/src/config/environment.ts b/typescript/infra/src/config/environment.ts index fbb9b74c7..5b6fa0350 100644 --- a/typescript/infra/src/config/environment.ts +++ b/typescript/infra/src/config/environment.ts @@ -1,3 +1,4 @@ +import { IRegistry } from '@hyperlane-xyz/registry'; import { BridgeAdapterConfig, ChainMap, @@ -5,9 +6,9 @@ import { ChainName, CoreConfig, IgpConfig, + MultiProtocolProvider, MultiProvider, OwnableConfig, - RpcConsensusType, } from '@hyperlane-xyz/sdk'; import { objKeys } from '@hyperlane-xyz/utils'; @@ -39,17 +40,20 @@ export const envNameToAgentEnv: Record = { export type EnvironmentConfig = { environment: DeployEnvironment; - chainMetadataConfigs: ChainMap; + supportedChainNames: ChainName[]; + // Get the registry with or without environment-specific secrets. + getRegistry: (useSecrets?: boolean) => Promise; // Each AgentConfig, keyed by the context agents: Partial>; core: ChainMap; igp: ChainMap; owners: ChainMap; infra: InfrastructureConfig; + getMultiProtocolProvider: () => Promise; getMultiProvider: ( context?: Contexts, role?: Role, - connectionType?: RpcConsensusType, + useSecrets?: boolean, ) => Promise; getKeys: ( context?: Contexts, diff --git a/typescript/infra/src/config/funding.ts b/typescript/infra/src/config/funding.ts index a55a6524f..f75d19cfd 100644 --- a/typescript/infra/src/config/funding.ts +++ b/typescript/infra/src/config/funding.ts @@ -1,4 +1,4 @@ -import { ChainMap, RpcConsensusType } from '@hyperlane-xyz/sdk'; +import { ChainMap } from '@hyperlane-xyz/sdk'; import { Contexts } from '../../config/contexts.js'; import { FundableRole, Role } from '../roles.js'; @@ -20,7 +20,6 @@ export interface KeyFunderConfig { contextsAndRolesToFund: ContextAndRolesMap; cyclesBetweenEthereumMessages?: number; prometheusPushGateway: string; - connectionType: RpcConsensusType; desiredBalancePerChain: ChainMap; desiredKathyBalancePerChain: ChainMap; } diff --git a/typescript/infra/src/config/helloworld/types.ts b/typescript/infra/src/config/helloworld/types.ts index 1345894d8..ba2be0750 100644 --- a/typescript/infra/src/config/helloworld/types.ts +++ b/typescript/infra/src/config/helloworld/types.ts @@ -1,4 +1,4 @@ -import { ChainMap, ChainName, RpcConsensusType } from '@hyperlane-xyz/sdk'; +import { ChainMap, ChainName } from '@hyperlane-xyz/sdk'; import { DockerConfig } from '../agent/agent.js'; @@ -29,7 +29,6 @@ export interface HelloWorldKathyConfig { messageReceiptTimeout: number; // Which type of provider to use - connectionType: RpcConsensusType; // How many cycles to skip between a cycles that send messages to/from Ethereum. Defaults to 0. cyclesBetweenEthereumMessages?: number; } diff --git a/typescript/infra/src/config/middleware.ts b/typescript/infra/src/config/middleware.ts index e6f25f92b..19fc3d848 100644 --- a/typescript/infra/src/config/middleware.ts +++ b/typescript/infra/src/config/middleware.ts @@ -1,10 +1,7 @@ -import { RpcConsensusType } from '@hyperlane-xyz/sdk'; - import { DockerConfig } from './agent/agent.js'; export interface LiquidityLayerRelayerConfig { docker: DockerConfig; namespace: string; - connectionType: RpcConsensusType.Single | RpcConsensusType.Quorum; prometheusPushGateway: string; } diff --git a/typescript/infra/src/funding/key-funder.ts b/typescript/infra/src/funding/key-funder.ts index b149b6a0b..ae29c7842 100644 --- a/typescript/infra/src/funding/key-funder.ts +++ b/typescript/infra/src/funding/key-funder.ts @@ -48,7 +48,6 @@ function getKeyFunderHelmValues( chains: agentConfig.environmentChainNames, contextFundingFrom: keyFunderConfig.contextFundingFrom, contextsAndRolesToFund: keyFunderConfig.contextsAndRolesToFund, - connectionType: keyFunderConfig.connectionType, desiredBalancePerChain: keyFunderConfig.desiredBalancePerChain, desiredKathyBalancePerChain: keyFunderConfig.desiredKathyBalancePerChain, }, diff --git a/typescript/infra/src/helloworld/kathy.ts b/typescript/infra/src/helloworld/kathy.ts index 44651380d..6366049fa 100644 --- a/typescript/infra/src/helloworld/kathy.ts +++ b/typescript/infra/src/helloworld/kathy.ts @@ -85,7 +85,6 @@ function getHelloworldKathyHelmValues( messageReceiptTimeout: kathyConfig.messageReceiptTimeout, cycleOnce, fullCycleTime, - connectionType: kathyConfig.connectionType, cyclesBetweenEthereumMessages: kathyConfig.cyclesBetweenEthereumMessages, }, image: { diff --git a/typescript/infra/src/middleware/liquidity-layer-relayer.ts b/typescript/infra/src/middleware/liquidity-layer-relayer.ts index e334c921e..4a1c3d7a1 100644 --- a/typescript/infra/src/middleware/liquidity-layer-relayer.ts +++ b/typescript/infra/src/middleware/liquidity-layer-relayer.ts @@ -44,7 +44,6 @@ function getLiquidityLayerRelayerHelmValues( runEnv: agentConfig.runEnv, // Only used for fetching RPC urls as env vars chains: agentConfig.contextChainNames, - connectionType: relayerConfig.connectionType, }, image: { repository: relayerConfig.docker.repo, diff --git a/typescript/infra/src/utils/gcloud.ts b/typescript/infra/src/utils/gcloud.ts index 0ce5e21f3..56d913b7c 100644 --- a/typescript/infra/src/utils/gcloud.ts +++ b/typescript/infra/src/utils/gcloud.ts @@ -1,3 +1,4 @@ +import { SecretManagerServiceClient } from '@google-cloud/secret-manager'; import fs from 'fs'; import { rootLogger } from '@hyperlane-xyz/utils'; @@ -6,6 +7,8 @@ import { rm, writeFile } from 'fs/promises'; import { execCmd, execCmdAndParseJson } from './utils.js'; +export const GCP_PROJECT_ID = 'abacus-labs-dev'; + interface IamCondition { title: string; expression: string; @@ -32,9 +35,7 @@ export async function fetchGCPSecret( ); output = envVarOverride; } else { - [output] = await execCmd( - `gcloud secrets versions access latest --secret ${secretName}`, - ); + output = await fetchLatestGCPSecret(secretName); } if (parseJson) { @@ -43,6 +44,27 @@ export async function fetchGCPSecret( return output; } +export async function fetchLatestGCPSecret(secretName: string) { + const client = await getSecretManagerServiceClient(); + const [secretVersion] = await client.accessSecretVersion({ + name: `projects/${GCP_PROJECT_ID}/secrets/${secretName}/versions/latest`, + }); + const secretData = secretVersion.payload?.data; + if (!secretData) { + throw new Error(`Secret ${secretName} missing payload`); + } + + // Handle both string and Uint8Array + let dataStr: string; + if (typeof secretData === 'string') { + dataStr = secretData; + } else { + dataStr = new TextDecoder().decode(secretData); + } + + return dataStr; +} + // If the environment variable GCP_SECRET_OVERRIDES_ENABLED is `true`, // this will attempt to find an environment variable of the form: // `GCP_SECRET_OVERRIDE_${gcpSecretName.replaceAll('-', '_').toUpperCase()}` @@ -60,6 +82,12 @@ function tryGCPSecretFromEnvVariable(gcpSecretName: string) { return process.env[overrideEnvVarName]; } +/** + * Checks if a secret exists in GCP using the gcloud CLI. + * @deprecated Use gcpSecretExistsUsingClient instead. + * @param secretName The name of the secret to check. + * @returns A boolean indicating whether the secret exists. + */ export async function gcpSecretExists(secretName: string) { const fullName = `projects/${await getCurrentProjectNumber()}/secrets/${secretName}`; debugLog(`Checking if GCP secret exists for ${fullName}`); @@ -71,6 +99,55 @@ export async function gcpSecretExists(secretName: string) { return matches.length > 0; } +/** + * Uses the SecretManagerServiceClient to check if a secret exists. + * @param secretName The name of the secret to check. + * @returns A boolean indicating whether the secret exists. + */ +export async function gcpSecretExistsUsingClient( + secretName: string, + client?: SecretManagerServiceClient, +): Promise { + if (!client) { + client = await getSecretManagerServiceClient(); + } + + try { + const fullSecretName = `projects/${await getCurrentProjectNumber()}/secrets/${secretName}`; + const [secrets] = await client.listSecrets({ + parent: `projects/${GCP_PROJECT_ID}`, + filter: `name=${fullSecretName}`, + }); + + return secrets.length > 0; + } catch (e) { + debugLog(`Error checking if secret exists: ${e}`); + throw e; + } +} + +export async function getGcpSecretLatestVersionName(secretName: string) { + const client = await getSecretManagerServiceClient(); + const [version] = await client.getSecretVersion({ + name: `projects/${GCP_PROJECT_ID}/secrets/${secretName}/versions/latest`, + }); + + return version?.name; +} + +export async function getSecretManagerServiceClient() { + return new SecretManagerServiceClient({ + projectId: GCP_PROJECT_ID, + }); +} + +/** + * Sets a GCP secret using the gcloud CLI. Create secret if it doesn't exist and add a new version or update the existing one. + * @deprecated Use setGCPSecretUsingClient instead. + * @param secretName The name of the secret to set. + * @param secret The secret to set. + * @param labels The labels to set on the secret. + */ export async function setGCPSecret( secretName: string, secret: string, @@ -97,6 +174,64 @@ export async function setGCPSecret( await rm(fileName); } +/** + * Sets a GCP secret using the SecretManagerServiceClient. Create secret if it doesn't exist and add a new version or update the existing one. + * @param secretName The name of the secret to set. + * @param secret The secret to set. + */ +export async function setGCPSecretUsingClient( + secretName: string, + secret: string, + labels?: Record, +) { + const client = await getSecretManagerServiceClient(); + + const exists = await gcpSecretExistsUsingClient(secretName, client); + if (!exists) { + // Create the secret + await client.createSecret({ + parent: `projects/${GCP_PROJECT_ID}`, + secretId: secretName, + secret: { + name: secretName, + replication: { + automatic: {}, + }, + labels, + }, + }); + debugLog(`Created new GCP secret for ${secretName}`); + } + await addGCPSecretVersion(secretName, secret, client); +} + +export async function addGCPSecretVersion( + secretName: string, + secret: string, + client?: SecretManagerServiceClient, +) { + if (!client) { + client = await getSecretManagerServiceClient(); + } + + const [version] = await client.addSecretVersion({ + parent: `projects/${GCP_PROJECT_ID}/secrets/${secretName}`, + payload: { + data: Buffer.from(secret, 'utf8'), + }, + }); + debugLog(`Added secret version ${version?.name}`); +} + +export async function disableGCPSecretVersion(secretName: string) { + const client = await getSecretManagerServiceClient(); + + const [version] = await client.disableSecretVersion({ + name: secretName, + }); + debugLog(`Disabled secret version ${version?.name}`); +} + // Returns the email of the service account export async function createServiceAccountIfNotExists( serviceAccountName: string, diff --git a/typescript/infra/src/utils/utils.ts b/typescript/infra/src/utils/utils.ts index 476fe3913..d3a8075ff 100644 --- a/typescript/infra/src/utils/utils.ts +++ b/typescript/infra/src/utils/utils.ts @@ -269,3 +269,7 @@ export function isEthereumProtocolChain(chainName: ChainName) { export function getInfraPath() { return join(dirname(fileURLToPath(import.meta.url)), '../../'); } + +export function inCIMode() { + return process.env.CI === 'true'; +} diff --git a/typescript/sdk/CHANGELOG.md b/typescript/sdk/CHANGELOG.md index 152e40bd6..b484d11bc 100644 --- a/typescript/sdk/CHANGELOG.md +++ b/typescript/sdk/CHANGELOG.md @@ -1,5 +1,21 @@ # @hyperlane-xyz/sdk +## 3.13.0 + +### Minor Changes + +- 39ea7cdef: Implement multi collateral warp routes +- babe816f8: Support xERC20 and xERC20 Lockbox in SDK and CLI +- 0cf692e73: Implement metadata builder fetching from message + +### Patch Changes + +- Updated dependencies [babe816f8] +- Updated dependencies [b440d98be] +- Updated dependencies [0cf692e73] + - @hyperlane-xyz/core@3.13.0 + - @hyperlane-xyz/utils@3.13.0 + ## 3.12.0 ### Minor Changes diff --git a/typescript/sdk/package.json b/typescript/sdk/package.json index 342287e22..ef58f43cd 100644 --- a/typescript/sdk/package.json +++ b/typescript/sdk/package.json @@ -1,13 +1,13 @@ { "name": "@hyperlane-xyz/sdk", "description": "The official SDK for the Hyperlane Network", - "version": "3.12.2", + "version": "3.13.0", "dependencies": { "@aws-sdk/client-s3": "^3.74.0", "@cosmjs/cosmwasm-stargate": "^0.31.3", "@cosmjs/stargate": "^0.31.3", - "@hyperlane-xyz/core": "3.12.2", - "@hyperlane-xyz/utils": "3.12.2", + "@hyperlane-xyz/core": "3.13.0", + "@hyperlane-xyz/utils": "3.13.0", "@safe-global/api-kit": "1.3.0", "@safe-global/protocol-kit": "1.3.0", "@solana/spl-token": "^0.3.8", diff --git a/typescript/sdk/src/consts/multisigIsm.ts b/typescript/sdk/src/consts/multisigIsm.ts index c6f8d8568..6dd1ba4c9 100644 --- a/typescript/sdk/src/consts/multisigIsm.ts +++ b/typescript/sdk/src/consts/multisigIsm.ts @@ -135,6 +135,11 @@ export const defaultMultisigConfigs: ChainMap = { ], }, + holesky: { + threshold: 1, + validators: ['0x7ab28ad88bb45867137ea823af88e2cb02359c03'], // TODO + }, + inevm: { threshold: 2, validators: [ diff --git a/typescript/sdk/src/metadata/agentConfig.ts b/typescript/sdk/src/metadata/agentConfig.ts index 3c16651f6..49f05e883 100644 --- a/typescript/sdk/src/metadata/agentConfig.ts +++ b/typescript/sdk/src/metadata/agentConfig.ts @@ -106,7 +106,7 @@ const AgentCosmosChainMetadataSchema = z.object({ amount: z .string() .regex(/^(\d*[.])?\d+$/) - .describe('The the gas price, in denom, to pay for each unit of gas'), + .describe('The gas price, in denom, to pay for each unit of gas'), }), contractAddressBytes: z .number() @@ -399,6 +399,16 @@ export function buildAgentConfig( const chainConfigs: ChainMap = {}; for (const chain of [...chains].sort()) { const metadata = multiProvider.tryGetChainMetadata(chain); + if (metadata?.protocol === ProtocolType.Cosmos) { + // Note: the gRPC URL format in the registry lacks a correct http:// or https:// prefix at the moment, + // which is expected by the agents. For now, we intentionally skip this. + delete metadata.grpcUrls; + + // The agents expect gasPrice.amount and gasPrice.denom and ignore the transaction overrides. + // To reduce confusion when looking at the config, we remove the transaction overrides. + delete metadata.transactionOverrides; + } + const chainConfig: AgentChainMetadata = { ...metadata, ...addresses[chain], diff --git a/typescript/utils/CHANGELOG.md b/typescript/utils/CHANGELOG.md index f72911518..0dce2d1b6 100644 --- a/typescript/utils/CHANGELOG.md +++ b/typescript/utils/CHANGELOG.md @@ -1,5 +1,11 @@ # @hyperlane-xyz/utils +## 3.13.0 + +### Minor Changes + +- 0cf692e73: Implement metadata builder fetching from message + ## 3.12.0 ### Minor Changes diff --git a/typescript/utils/package.json b/typescript/utils/package.json index 094b5b053..260348910 100644 --- a/typescript/utils/package.json +++ b/typescript/utils/package.json @@ -1,7 +1,7 @@ { "name": "@hyperlane-xyz/utils", "description": "General utilities and types for the Hyperlane network", - "version": "3.12.2", + "version": "3.13.0", "dependencies": { "@cosmjs/encoding": "^0.31.3", "@solana/web3.js": "^1.78.0", diff --git a/yarn.lock b/yarn.lock index f8e0a469e..8b293bfc7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5608,6 +5608,39 @@ __metadata: languageName: node linkType: hard +"@google-cloud/secret-manager@npm:^5.5.0": + version: 5.5.0 + resolution: "@google-cloud/secret-manager@npm:5.5.0" + dependencies: + google-gax: "npm:^4.0.3" + checksum: 487267dab1e260a0da79012194bb61c85f8b02b642330fdec32cac1fe37900f0fd6709ff4928fe631ab227b0d758bd3e59b1e3dc1d0682de566a64ef4fb42bba + languageName: node + linkType: hard + +"@grpc/grpc-js@npm:~1.10.3": + version: 1.10.8 + resolution: "@grpc/grpc-js@npm:1.10.8" + dependencies: + "@grpc/proto-loader": "npm:^0.7.13" + "@js-sdsl/ordered-map": "npm:^4.4.2" + checksum: cb7903e93db38a86bd2ddffb84313de78144454ad988801ede90f0c794d6a5f666a1b24f50e950b50d633b4bacc7416c7cabf4a6791b91c4fa89c001122edba8 + languageName: node + linkType: hard + +"@grpc/proto-loader@npm:^0.7.0, @grpc/proto-loader@npm:^0.7.13": + version: 0.7.13 + resolution: "@grpc/proto-loader@npm:0.7.13" + dependencies: + lodash.camelcase: "npm:^4.3.0" + long: "npm:^5.0.0" + protobufjs: "npm:^7.2.5" + yargs: "npm:^17.7.2" + bin: + proto-loader-gen-types: build/bin/proto-loader-gen-types.js + checksum: 7e2d842c2061cbaf6450c71da0077263be3bab165454d5c8a3e1ae4d3c6d2915f02fd27da63ff01f05e127b1221acd40705273f5d29303901e60514e852992f4 + languageName: node + linkType: hard + "@headlessui/react@npm:^1.7.17": version: 1.7.18 resolution: "@headlessui/react@npm:1.7.18" @@ -5691,8 +5724,8 @@ __metadata: "@aws-sdk/client-kms": "npm:^3.577.0" "@aws-sdk/client-s3": "npm:^3.577.0" "@hyperlane-xyz/registry": "npm:1.3.0" - "@hyperlane-xyz/sdk": "npm:3.12.2" - "@hyperlane-xyz/utils": "npm:3.12.2" + "@hyperlane-xyz/sdk": "npm:3.13.0" + "@hyperlane-xyz/utils": "npm:3.13.0" "@inquirer/prompts": "npm:^3.0.0" "@types/mocha": "npm:^10.0.1" "@types/node": "npm:^18.14.5" @@ -5720,12 +5753,12 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/core@npm:3.12.2, @hyperlane-xyz/core@workspace:solidity": +"@hyperlane-xyz/core@npm:3.13.0, @hyperlane-xyz/core@workspace:solidity": version: 0.0.0-use.local resolution: "@hyperlane-xyz/core@workspace:solidity" dependencies: "@eth-optimism/contracts": "npm:^0.6.0" - "@hyperlane-xyz/utils": "npm:3.12.2" + "@hyperlane-xyz/utils": "npm:3.13.0" "@layerzerolabs/lz-evm-oapp-v2": "npm:2.0.2" "@layerzerolabs/solidity-examples": "npm:^1.1.0" "@nomiclabs/hardhat-ethers": "npm:^2.2.3" @@ -5773,13 +5806,13 @@ __metadata: languageName: node linkType: hard -"@hyperlane-xyz/helloworld@npm:3.12.2, @hyperlane-xyz/helloworld@workspace:typescript/helloworld": +"@hyperlane-xyz/helloworld@npm:3.13.0, @hyperlane-xyz/helloworld@workspace:typescript/helloworld": version: 0.0.0-use.local resolution: "@hyperlane-xyz/helloworld@workspace:typescript/helloworld" dependencies: - "@hyperlane-xyz/core": "npm:3.12.2" + "@hyperlane-xyz/core": "npm:3.13.0" "@hyperlane-xyz/registry": "npm:1.3.0" - "@hyperlane-xyz/sdk": "npm:3.12.2" + "@hyperlane-xyz/sdk": "npm:3.13.0" "@nomiclabs/hardhat-ethers": "npm:^2.2.3" "@nomiclabs/hardhat-waffle": "npm:^2.0.6" "@openzeppelin/contracts-upgradeable": "npm:^4.9.3" @@ -5824,10 +5857,11 @@ __metadata: "@ethersproject/experimental": "npm:^5.7.0" "@ethersproject/hardware-wallets": "npm:^5.7.0" "@ethersproject/providers": "npm:^5.7.2" - "@hyperlane-xyz/helloworld": "npm:3.12.2" + "@google-cloud/secret-manager": "npm:^5.5.0" + "@hyperlane-xyz/helloworld": "npm:3.13.0" "@hyperlane-xyz/registry": "npm:1.3.0" - "@hyperlane-xyz/sdk": "npm:3.12.2" - "@hyperlane-xyz/utils": "npm:3.12.2" + "@hyperlane-xyz/sdk": "npm:3.13.0" + "@hyperlane-xyz/utils": "npm:3.13.0" "@nomiclabs/hardhat-ethers": "npm:^2.2.3" "@nomiclabs/hardhat-etherscan": "npm:^3.0.3" "@nomiclabs/hardhat-waffle": "npm:^2.0.6" @@ -5887,15 +5921,15 @@ __metadata: languageName: node linkType: hard -"@hyperlane-xyz/sdk@npm:3.12.2, @hyperlane-xyz/sdk@workspace:typescript/sdk": +"@hyperlane-xyz/sdk@npm:3.13.0, @hyperlane-xyz/sdk@workspace:typescript/sdk": version: 0.0.0-use.local resolution: "@hyperlane-xyz/sdk@workspace:typescript/sdk" dependencies: "@aws-sdk/client-s3": "npm:^3.74.0" "@cosmjs/cosmwasm-stargate": "npm:^0.31.3" "@cosmjs/stargate": "npm:^0.31.3" - "@hyperlane-xyz/core": "npm:3.12.2" - "@hyperlane-xyz/utils": "npm:3.12.2" + "@hyperlane-xyz/core": "npm:3.13.0" + "@hyperlane-xyz/utils": "npm:3.13.0" "@nomiclabs/hardhat-ethers": "npm:^2.2.3" "@nomiclabs/hardhat-waffle": "npm:^2.0.6" "@safe-global/api-kit": "npm:1.3.0" @@ -5963,7 +5997,7 @@ __metadata: languageName: node linkType: hard -"@hyperlane-xyz/utils@npm:3.12.2, @hyperlane-xyz/utils@workspace:typescript/utils": +"@hyperlane-xyz/utils@npm:3.13.0, @hyperlane-xyz/utils@workspace:typescript/utils": version: 0.0.0-use.local resolution: "@hyperlane-xyz/utils@workspace:typescript/utils" dependencies: @@ -6501,6 +6535,13 @@ __metadata: languageName: node linkType: hard +"@js-sdsl/ordered-map@npm:^4.4.2": + version: 4.4.2 + resolution: "@js-sdsl/ordered-map@npm:4.4.2" + checksum: ac64e3f0615ecc015461c9f527f124d2edaa9e68de153c1e270c627e01e83d046522d7e872692fd57a8c514578b539afceff75831c0d8b2a9a7a347fbed35af4 + languageName: node + linkType: hard + "@layerzerolabs/lz-evm-messagelib-v2@npm:^2.0.2": version: 2.0.6 resolution: "@layerzerolabs/lz-evm-messagelib-v2@npm:2.0.6" @@ -9439,6 +9480,13 @@ __metadata: languageName: node linkType: hard +"@types/caseless@npm:*": + version: 0.12.5 + resolution: "@types/caseless@npm:0.12.5" + checksum: f6a3628add76d27005495914c9c3873a93536957edaa5b69c63b46fe10b4649a6fecf16b676c1695f46aab851da47ec6047dcf3570fa8d9b6883492ff6d074e0 + languageName: node + linkType: hard + "@types/chai@npm:*, @types/chai@npm:^4.2.21": version: 4.3.1 resolution: "@types/chai@npm:4.3.1" @@ -9607,7 +9655,7 @@ __metadata: languageName: node linkType: hard -"@types/long@npm:^4.0.1": +"@types/long@npm:^4.0.0, @types/long@npm:^4.0.1": version: 4.0.2 resolution: "@types/long@npm:4.0.2" checksum: 68afa05fb20949d88345876148a76f6ccff5433310e720db51ac5ca21cb8cc6714286dbe04713840ddbd25a8b56b7a23aa87d08472fabf06463a6f2ed4967707 @@ -9814,6 +9862,18 @@ __metadata: languageName: node linkType: hard +"@types/request@npm:^2.48.8": + version: 2.48.12 + resolution: "@types/request@npm:2.48.12" + dependencies: + "@types/caseless": "npm:*" + "@types/node": "npm:*" + "@types/tough-cookie": "npm:*" + form-data: "npm:^2.5.0" + checksum: a7b3f9f14cacc18fe235bb8e57eff1232a04bd3fa3dad29371f24a5d96db2cd295a0c8b6b34ed7efa3efbbcff845febb02c9635cd68c54811c947ea66ae22090 + languageName: node + linkType: hard + "@types/resolve@npm:^0.0.8": version: 0.0.8 resolution: "@types/resolve@npm:0.0.8" @@ -9904,6 +9964,13 @@ __metadata: languageName: node linkType: hard +"@types/tough-cookie@npm:*": + version: 4.0.5 + resolution: "@types/tough-cookie@npm:4.0.5" + checksum: 01fd82efc8202670865928629697b62fe9bf0c0dcbc5b1c115831caeb073a2c0abb871ff393d7df1ae94ea41e256cb87d2a5a91fd03cdb1b0b4384e08d4ee482 + languageName: node + linkType: hard + "@types/trusted-types@npm:^2.0.2": version: 2.0.7 resolution: "@types/trusted-types@npm:2.0.7" @@ -10902,6 +10969,15 @@ __metadata: languageName: node linkType: hard +"agent-base@npm:^7.0.2": + version: 7.1.1 + resolution: "agent-base@npm:7.1.1" + dependencies: + debug: "npm:^4.3.4" + checksum: c478fec8f79953f118704d007a38f2a185458853f5c45579b9669372bd0e12602e88dc2ad0233077831504f7cd6fcc8251c383375bba5eaaf563b102938bda26 + languageName: node + linkType: hard + "agentkeepalive@npm:^4.2.1": version: 4.2.1 resolution: "agentkeepalive@npm:4.2.1" @@ -11956,6 +12032,13 @@ __metadata: languageName: node linkType: hard +"buffer-equal-constant-time@npm:1.0.1": + version: 1.0.1 + resolution: "buffer-equal-constant-time@npm:1.0.1" + checksum: 80bb945f5d782a56f374b292770901065bad21420e34936ecbe949e57724b4a13874f735850dd1cc61f078773c4fb5493a41391e7bda40d1fa388d6bd80daaab + languageName: node + linkType: hard + "buffer-from@npm:^1.0.0": version: 1.1.2 resolution: "buffer-from@npm:1.1.2" @@ -13651,6 +13734,18 @@ __metadata: languageName: node linkType: hard +"duplexify@npm:^4.0.0": + version: 4.1.3 + resolution: "duplexify@npm:4.1.3" + dependencies: + end-of-stream: "npm:^1.4.1" + inherits: "npm:^2.0.3" + readable-stream: "npm:^3.1.1" + stream-shift: "npm:^1.0.2" + checksum: b44b98ba0ffac3a658b4b1bf877219e996db288c5ae6f3dc55ca9b2cbef7df60c10eabfdd947f3d73a623eb9975a74a66d6d61e6f26bff90155315adb362aa77 + languageName: node + linkType: hard + "duplexify@npm:^4.1.2": version: 4.1.2 resolution: "duplexify@npm:4.1.2" @@ -13680,6 +13775,15 @@ __metadata: languageName: node linkType: hard +"ecdsa-sig-formatter@npm:1.0.11, ecdsa-sig-formatter@npm:^1.0.11": + version: 1.0.11 + resolution: "ecdsa-sig-formatter@npm:1.0.11" + dependencies: + safe-buffer: "npm:^5.0.1" + checksum: 878e1aab8a42773320bc04c6de420bee21aebd71810e40b1799880a8a1c4594bcd6adc3d4213a0fb8147d4c3f529d8f9a618d7f59ad5a9a41b142058aceda23f + languageName: node + linkType: hard + "ee-first@npm:1.1.1": version: 1.1.1 resolution: "ee-first@npm:1.1.1" @@ -14912,7 +15016,7 @@ __metadata: languageName: node linkType: hard -"extend@npm:~3.0.2": +"extend@npm:^3.0.2, extend@npm:~3.0.2": version: 3.0.2 resolution: "extend@npm:3.0.2" checksum: 59e89e2dc798ec0f54b36d82f32a27d5f6472c53974f61ca098db5d4648430b725387b53449a34df38fd0392045434426b012f302b3cc049a6500ccf82877e4e @@ -15303,7 +15407,7 @@ __metadata: languageName: node linkType: hard -"form-data@npm:^2.2.0": +"form-data@npm:^2.2.0, form-data@npm:^2.5.0": version: 2.5.1 resolution: "form-data@npm:2.5.1" dependencies: @@ -15666,6 +15770,29 @@ __metadata: languageName: node linkType: hard +"gaxios@npm:^6.0.0, gaxios@npm:^6.1.1": + version: 6.6.0 + resolution: "gaxios@npm:6.6.0" + dependencies: + extend: "npm:^3.0.2" + https-proxy-agent: "npm:^7.0.1" + is-stream: "npm:^2.0.0" + node-fetch: "npm:^2.6.9" + uuid: "npm:^9.0.1" + checksum: 9f035590374fd168e7bb3ddda369fc8bd487f16a2308fde18284ccc0f685d0af4ac5e3e38d680a8c6342a9000fbf9d77ce691ee110dbed2feebb659e729c640a + languageName: node + linkType: hard + +"gcp-metadata@npm:^6.1.0": + version: 6.1.0 + resolution: "gcp-metadata@npm:6.1.0" + dependencies: + gaxios: "npm:^6.0.0" + json-bigint: "npm:^1.0.0" + checksum: a0d12a9cb7499fdb9de0fff5406aa220310c1326b80056be8d9b747aae26414f99d14bd795c0ec52ef7d0473eef9d61bb657b8cd3d8186c8a84c4ddbff025fe9 + languageName: node + linkType: hard + "gensync@npm:^1.0.0-beta.2": version: 1.0.0-beta.2 resolution: "gensync@npm:1.0.0-beta.2" @@ -16034,6 +16161,40 @@ __metadata: languageName: node linkType: hard +"google-auth-library@npm:^9.3.0": + version: 9.10.0 + resolution: "google-auth-library@npm:9.10.0" + dependencies: + base64-js: "npm:^1.3.0" + ecdsa-sig-formatter: "npm:^1.0.11" + gaxios: "npm:^6.1.1" + gcp-metadata: "npm:^6.1.0" + gtoken: "npm:^7.0.0" + jws: "npm:^4.0.0" + checksum: 10d5863493f9426107b0f6c4df244b1413a2cacff9589076f906924336d894fe8bc4153d4a3756cebec8a58784ff1a3900c621924f75f004908611fa46d3caa6 + languageName: node + linkType: hard + +"google-gax@npm:^4.0.3": + version: 4.3.3 + resolution: "google-gax@npm:4.3.3" + dependencies: + "@grpc/grpc-js": "npm:~1.10.3" + "@grpc/proto-loader": "npm:^0.7.0" + "@types/long": "npm:^4.0.0" + abort-controller: "npm:^3.0.0" + duplexify: "npm:^4.0.0" + google-auth-library: "npm:^9.3.0" + node-fetch: "npm:^2.6.1" + object-hash: "npm:^3.0.0" + proto3-json-serializer: "npm:^2.0.0" + protobufjs: "npm:7.2.6" + retry-request: "npm:^7.0.0" + uuid: "npm:^9.0.1" + checksum: 63335724e741737b90689e43f8ea5804d82b8f4eaa013ba07166bf6119ef7474d06682d580d72f6b708d6c55251204b1f05db615c3cd84abf2f8f295c50882ec + languageName: node + linkType: hard + "gopd@npm:^1.0.1": version: 1.0.1 resolution: "gopd@npm:1.0.1" @@ -16166,6 +16327,16 @@ __metadata: languageName: node linkType: hard +"gtoken@npm:^7.0.0": + version: 7.1.0 + resolution: "gtoken@npm:7.1.0" + dependencies: + gaxios: "npm:^6.0.0" + jws: "npm:^4.0.0" + checksum: 640392261e55c9242137a81a4af8feb053b57061762cedddcbb6a0d62c2314316161808ac2529eea67d06d69fdc56d82361af50f2d840a04a87ea29e124d7382 + languageName: node + linkType: hard + "h3@npm:^1.10.1, h3@npm:^1.8.2": version: 1.10.2 resolution: "h3@npm:1.10.2" @@ -16725,6 +16896,16 @@ __metadata: languageName: node linkType: hard +"https-proxy-agent@npm:^7.0.1": + version: 7.0.4 + resolution: "https-proxy-agent@npm:7.0.4" + dependencies: + agent-base: "npm:^7.0.2" + debug: "npm:4" + checksum: 405fe582bba461bfe5c7e2f8d752b384036854488b828ae6df6a587c654299cbb2c50df38c4b6ab303502c3c5e029a793fbaac965d1e86ee0be03faceb554d63 + languageName: node + linkType: hard + "human-id@npm:^1.0.2": version: 1.0.2 resolution: "human-id@npm:1.0.2" @@ -18336,6 +18517,27 @@ __metadata: languageName: node linkType: hard +"jwa@npm:^2.0.0": + version: 2.0.0 + resolution: "jwa@npm:2.0.0" + dependencies: + buffer-equal-constant-time: "npm:1.0.1" + ecdsa-sig-formatter: "npm:1.0.11" + safe-buffer: "npm:^5.0.1" + checksum: ab983f6685d99d13ddfbffef9b1c66309a536362a8412d49ba6e687d834a1240ce39290f30ac7dbe241e0ab6c76fee7ff795776ce534e11d148158c9b7193498 + languageName: node + linkType: hard + +"jws@npm:^4.0.0": + version: 4.0.0 + resolution: "jws@npm:4.0.0" + dependencies: + jwa: "npm:^2.0.0" + safe-buffer: "npm:^5.0.1" + checksum: 1d15f4cdea376c6bd6a81002bd2cb0bf3d51d83da8f0727947b5ba3e10cf366721b8c0d099bf8c1eb99eb036e2c55e5fd5efd378ccff75a2b4e0bd10002348b9 + languageName: node + linkType: hard + "keccak@npm:3.0.1": version: 3.0.1 resolution: "keccak@npm:3.0.1" @@ -18918,6 +19120,13 @@ __metadata: languageName: node linkType: hard +"long@npm:^5.0.0": + version: 5.2.3 + resolution: "long@npm:5.2.3" + checksum: 9167ec6947a825b827c30da169a7384eec6c0c9ec2f0b9c74da2e93d81159bbe39fb09c3f13dae9721d4b807ccfa09797a7dd1012f5d478e3e33ca3c78b608e6 + languageName: node + linkType: hard + "loose-envify@npm:^1.0.0, loose-envify@npm:^1.1.0": version: 1.4.0 resolution: "loose-envify@npm:1.4.0" @@ -20113,7 +20322,7 @@ __metadata: languageName: node linkType: hard -"node-fetch@npm:^2.6.0, node-fetch@npm:^2.6.1, node-fetch@npm:^2.6.12": +"node-fetch@npm:^2.6.0, node-fetch@npm:^2.6.1, node-fetch@npm:^2.6.12, node-fetch@npm:^2.6.9": version: 2.7.0 resolution: "node-fetch@npm:2.7.0" dependencies: @@ -20422,6 +20631,13 @@ __metadata: languageName: node linkType: hard +"object-hash@npm:^3.0.0": + version: 3.0.0 + resolution: "object-hash@npm:3.0.0" + checksum: f498d456a20512ba7be500cef4cf7b3c183cc72c65372a549c9a0e6dd78ce26f375e9b1315c07592d3fde8f10d5019986eba35970570d477ed9a2a702514432a + languageName: node + linkType: hard + "object-inspect@npm:^1.12.0, object-inspect@npm:^1.12.2, object-inspect@npm:^1.9.0": version: 1.12.2 resolution: "object-inspect@npm:1.12.2" @@ -21344,6 +21560,35 @@ __metadata: languageName: node linkType: hard +"proto3-json-serializer@npm:^2.0.0": + version: 2.0.1 + resolution: "proto3-json-serializer@npm:2.0.1" + dependencies: + protobufjs: "npm:^7.2.5" + checksum: dc4319c90e2412b9647f13dd1df2a6338ee3a07e2fd693c5ce4d1728c3730d913ebdb6d656f400ae4214a70bf0791ca0bc04d53b2cbdd75394bf0b175898443b + languageName: node + linkType: hard + +"protobufjs@npm:7.2.6": + version: 7.2.6 + resolution: "protobufjs@npm:7.2.6" + dependencies: + "@protobufjs/aspromise": "npm:^1.1.2" + "@protobufjs/base64": "npm:^1.1.2" + "@protobufjs/codegen": "npm:^2.0.4" + "@protobufjs/eventemitter": "npm:^1.1.0" + "@protobufjs/fetch": "npm:^1.1.0" + "@protobufjs/float": "npm:^1.0.2" + "@protobufjs/inquire": "npm:^1.1.0" + "@protobufjs/path": "npm:^1.1.2" + "@protobufjs/pool": "npm:^1.1.0" + "@protobufjs/utf8": "npm:^1.1.0" + "@types/node": "npm:>=13.7.0" + long: "npm:^5.0.0" + checksum: 81ab853d28c71998d056d6b34f83c4bc5be40cb0b416585f99ed618aed833d64b2cf89359bad7474d345302f2b5e236c4519165f8483d7ece7fd5b0d9ac13f8b + languageName: node + linkType: hard + "protobufjs@npm:^6.8.8, protobufjs@npm:~6.11.2, protobufjs@npm:~6.11.3": version: 6.11.4 resolution: "protobufjs@npm:6.11.4" @@ -21368,6 +21613,26 @@ __metadata: languageName: node linkType: hard +"protobufjs@npm:^7.2.5": + version: 7.3.0 + resolution: "protobufjs@npm:7.3.0" + dependencies: + "@protobufjs/aspromise": "npm:^1.1.2" + "@protobufjs/base64": "npm:^1.1.2" + "@protobufjs/codegen": "npm:^2.0.4" + "@protobufjs/eventemitter": "npm:^1.1.0" + "@protobufjs/fetch": "npm:^1.1.0" + "@protobufjs/float": "npm:^1.0.2" + "@protobufjs/inquire": "npm:^1.1.0" + "@protobufjs/path": "npm:^1.1.2" + "@protobufjs/pool": "npm:^1.1.0" + "@protobufjs/utf8": "npm:^1.1.0" + "@types/node": "npm:>=13.7.0" + long: "npm:^5.0.0" + checksum: aff4aa2a3a2f011accb51e23fcae122acbee35cb761abe51f799675a61ab39ad9a506911f307e0fdb9a1703bed1f522cfbdaafaeefd2b3aaca2ddc18f03029d9 + languageName: node + linkType: hard + "proxy-addr@npm:~2.0.7": version: 2.0.7 resolution: "proxy-addr@npm:2.0.7" @@ -22253,6 +22518,17 @@ __metadata: languageName: node linkType: hard +"retry-request@npm:^7.0.0": + version: 7.0.2 + resolution: "retry-request@npm:7.0.2" + dependencies: + "@types/request": "npm:^2.48.8" + extend: "npm:^3.0.2" + teeny-request: "npm:^9.0.0" + checksum: 8f4c927d41dd575fc460aad7b762fb0a33542097201c3c1a31529ad17fa8af3ac0d2a45bf4a2024d079913e9c2dd431566070fe33321c667ac87ebb400de5917 + languageName: node + linkType: hard + "retry@npm:0.13.1": version: 0.13.1 resolution: "retry@npm:0.13.1" @@ -23390,7 +23666,16 @@ __metadata: languageName: node linkType: hard -"stream-shift@npm:^1.0.0": +"stream-events@npm:^1.0.5": + version: 1.0.5 + resolution: "stream-events@npm:1.0.5" + dependencies: + stubs: "npm:^3.0.0" + checksum: 969ce82e34bfbef5734629cc06f9d7f3705a9ceb8fcd6a526332f9159f1f8bbfdb1a453f3ced0b728083454f7706adbbe8428bceb788a0287ca48ba2642dc3fc + languageName: node + linkType: hard + +"stream-shift@npm:^1.0.0, stream-shift@npm:^1.0.2": version: 1.0.3 resolution: "stream-shift@npm:1.0.3" checksum: a24c0a3f66a8f9024bd1d579a533a53be283b4475d4e6b4b3211b964031447bdf6532dd1f3c2b0ad66752554391b7c62bd7ca4559193381f766534e723d50242 @@ -23690,6 +23975,13 @@ __metadata: languageName: node linkType: hard +"stubs@npm:^3.0.0": + version: 3.0.0 + resolution: "stubs@npm:3.0.0" + checksum: dec7b82186e3743317616235c59bfb53284acc312cb9f4c3e97e2205c67a5c158b0ca89db5927e52351582e90a2672822eeaec9db396e23e56893d2a8676e024 + languageName: node + linkType: hard + "styled-jsx@npm:5.1.1": version: 5.1.1 resolution: "styled-jsx@npm:5.1.1" @@ -23943,6 +24235,19 @@ __metadata: languageName: node linkType: hard +"teeny-request@npm:^9.0.0": + version: 9.0.0 + resolution: "teeny-request@npm:9.0.0" + dependencies: + http-proxy-agent: "npm:^5.0.0" + https-proxy-agent: "npm:^5.0.0" + node-fetch: "npm:^2.6.9" + stream-events: "npm:^1.0.5" + uuid: "npm:^9.0.0" + checksum: 44daabb6c2e239c3daed0218ebdafb50c7141c16d7257a6cfef786dbff56d7853c2c02c97934f7ed57818ce5861ac16c5f52f3a16fa292bd4caf53483d386443 + languageName: node + linkType: hard + "term-size@npm:^2.1.0": version: 2.2.1 resolution: "term-size@npm:2.2.1"