From 878bb880855ff55a982589daf7b87a6ad5883f02 Mon Sep 17 00:00:00 2001 From: Lee <6251863+ltyu@users.noreply.github.com> Date: Wed, 29 Jan 2025 09:49:37 -0500 Subject: [PATCH 01/11] fix: Update Renzo to use hardcoded tokenPrices (#5305) ### Description Update Renzo to use hardcoded tokenPrices because tokenPrices.json get updated which cause the hook configs to be different. ### Backward compatibility Yes --- .../configGetters/getRenzoEZETHWarpConfig.ts | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getRenzoEZETHWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getRenzoEZETHWarpConfig.ts index 93ea7df1ef..6d351483d3 100644 --- a/typescript/infra/config/environments/mainnet3/warp/configGetters/getRenzoEZETHWarpConfig.ts +++ b/typescript/infra/config/environments/mainnet3/warp/configGetters/getRenzoEZETHWarpConfig.ts @@ -16,9 +16,7 @@ import { Address, assert, symmetricDifference } from '@hyperlane-xyz/utils'; import { getEnvironmentConfig } from '../../../../../scripts/core-utils.js'; import { getRegistry as getMainnet3Registry } from '../../chains.js'; -import rawTokenPrices from '../../tokenPrices.json'; -const tokenPrices: ChainMap = rawTokenPrices; const chainsToDeploy = [ 'arbitrum', 'optimism', @@ -36,6 +34,21 @@ const chainsToDeploy = [ ]; export const MAX_PROTOCOL_FEE = parseEther('100').toString(); // Changing this will redeploy the PROTOCOL_FEE hook +const tokenPrices: ChainMap = { + arbitrum: '3157.26', + optimism: '3157.26', + base: '3157.26', + blast: '3157.26', + bsc: '673.59', + mode: '3157.26', + linea: '3157.26', + ethereum: '3157.26', + fraxtal: '3168.75', + zircuit: '3157.26', + taiko: '3157.26', + sei: '0.354988', + swell: '3157.26', +}; export function getProtocolFee(chain: ChainName) { return (0.5 / Number(tokenPrices[chain])).toFixed(10).toString(); // ~$0.50 USD } @@ -298,6 +311,10 @@ export const getRenzoEZETHWarpConfig = async (): Promise< new Set(chainsToDeploy), new Set(Object.keys(xERC20)), ); + const tokenPriceDiff = symmetricDifference( + new Set(chainsToDeploy), + new Set(Object.keys(tokenPrices)), + ); if (validatorDiff.size > 0) { throw new Error( `chainsToDeploy !== validatorConfig, diff is ${Array.from( @@ -318,6 +335,14 @@ export const getRenzoEZETHWarpConfig = async (): Promise< ); } + if (tokenPriceDiff.size > 0) { + throw new Error( + `chainsToDeploy !== xERC20Diff, diff is ${Array.from(xERC20Diff).join( + ', ', + )}`, + ); + } + const tokenConfig = Object.fromEntries( await Promise.all( chainsToDeploy.map( From f250b193fa29bf9791421ea2b24f0702e143879d Mon Sep 17 00:00:00 2001 From: Jeff Date: Wed, 29 Jan 2025 23:52:37 +0800 Subject: [PATCH 02/11] feat: attempt to fetch operation status in database upon relayer restart (#5182) ### Description - Add logic to retrieve message status from db before defaulting to `PendingOperationStatus::FirstPrepareAttempt`. - Add e2e tests to restart relayer and check message statuses were successfully read from db ### Drive-by changes - add `impl Default` to MerkleTreeBuilder to satisfy `clippy` ### Related issues - Fixes https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/5060 ### Backward compatibility Yes ### Testing - Add e2e tests to restart relayer and check message statuses were successfully read from db --------- Co-authored-by: Daniel Savu <23065004+daniel-savu@users.noreply.github.com> --- rust/main/agents/relayer/src/lib.rs | 3 +- .../agents/relayer/src/merkle_tree/builder.rs | 6 + rust/main/agents/relayer/src/msg/mod.rs | 3 +- .../agents/relayer/src/msg/op_submitter.rs | 10 +- .../agents/relayer/src/msg/pending_message.rs | 64 +++- .../src/invariants/termination_invariants.rs | 44 ++- rust/main/utils/run-locally/src/main.rs | 328 ++++++++++++------ rust/main/utils/run-locally/src/utils.rs | 21 +- 8 files changed, 328 insertions(+), 151 deletions(-) diff --git a/rust/main/agents/relayer/src/lib.rs b/rust/main/agents/relayer/src/lib.rs index 9a6e1e4147..082fd0b595 100644 --- a/rust/main/agents/relayer/src/lib.rs +++ b/rust/main/agents/relayer/src/lib.rs @@ -1,5 +1,6 @@ +pub mod msg; + mod merkle_tree; -mod msg; mod processor; mod prover; mod relayer; diff --git a/rust/main/agents/relayer/src/merkle_tree/builder.rs b/rust/main/agents/relayer/src/merkle_tree/builder.rs index 876200f96a..e8b5462b51 100644 --- a/rust/main/agents/relayer/src/merkle_tree/builder.rs +++ b/rust/main/agents/relayer/src/merkle_tree/builder.rs @@ -38,6 +38,12 @@ impl Display for MerkleTreeBuilder { } } +impl Default for MerkleTreeBuilder { + fn default() -> Self { + Self::new() + } +} + /// MerkleTreeBuilder errors #[derive(Debug, thiserror::Error)] pub enum MerkleTreeBuilderError { diff --git a/rust/main/agents/relayer/src/msg/mod.rs b/rust/main/agents/relayer/src/msg/mod.rs index e47015709c..2f13832719 100644 --- a/rust/main/agents/relayer/src/msg/mod.rs +++ b/rust/main/agents/relayer/src/msg/mod.rs @@ -32,7 +32,8 @@ pub(crate) mod gas_payment; pub(crate) mod metadata; pub(crate) mod op_queue; pub(crate) mod op_submitter; -pub(crate) mod pending_message; pub(crate) mod processor; +pub mod pending_message; + pub use gas_payment::GAS_EXPENDITURE_LOG_MESSAGE; diff --git a/rust/main/agents/relayer/src/msg/op_submitter.rs b/rust/main/agents/relayer/src/msg/op_submitter.rs index 0c9b284a83..67524e80b0 100644 --- a/rust/main/agents/relayer/src/msg/op_submitter.rs +++ b/rust/main/agents/relayer/src/msg/op_submitter.rs @@ -230,14 +230,8 @@ async fn receive_task( // make sure things are getting wired up correctly; if this works in testing it // should also be valid in production. debug_assert_eq!(*op.destination_domain(), domain); - let status = op.retrieve_status_from_db().unwrap_or_else(|| { - trace!( - ?op, - "No status found for message, defaulting to FirstPrepareAttempt" - ); - PendingOperationStatus::FirstPrepareAttempt - }); - prepare_queue.push(op, Some(status)).await; + let op_status = op.status(); + prepare_queue.push(op, Some(op_status)).await; } } diff --git a/rust/main/agents/relayer/src/msg/pending_message.rs b/rust/main/agents/relayer/src/msg/pending_message.rs index 92d279856d..0d8a090c78 100644 --- a/rust/main/agents/relayer/src/msg/pending_message.rs +++ b/rust/main/agents/relayer/src/msg/pending_message.rs @@ -21,7 +21,7 @@ use hyperlane_core::{ }; use prometheus::{IntCounter, IntGauge}; use serde::Serialize; -use tracing::{debug, error, info, info_span, instrument, trace, warn, Instrument}; +use tracing::{debug, error, info, info_span, instrument, trace, warn, Instrument, Level}; use super::{ gas_payment::{GasPaymentEnforcer, GasPolicyStatus}, @@ -36,6 +36,8 @@ pub const CONFIRM_DELAY: Duration = if cfg!(any(test, feature = "test-utils")) { Duration::from_secs(60 * 10) }; +pub const RETRIEVED_MESSAGE_LOG: &str = "Message status retrieved from db"; + /// The message context contains the links needed to submit a message. Each /// instance is for a unique origin -> destination pairing. pub struct MessageContext { @@ -510,27 +512,53 @@ impl PendingMessage { ctx: Arc, app_context: Option, ) -> Self { - let mut pm = Self::new( - message, - ctx, - // Since we don't persist the message status for now, assume it's the first attempt - PendingOperationStatus::FirstPrepareAttempt, - app_context, - ); - match pm - .ctx + // Attempt to fetch status about message from database + let message_status = match ctx.origin_db.retrieve_status_by_message_id(&message.id()) { + Ok(Some(status)) => { + // This event is used for E2E tests to ensure message statuses + // are being properly loaded from the db + tracing::event!( + if cfg!(feature = "test-utils") { + Level::DEBUG + } else { + Level::TRACE + }, + ?status, + id=?message.id(), + RETRIEVED_MESSAGE_LOG, + ); + status + } + _ => { + tracing::event!( + if cfg!(feature = "test-utils") { + Level::DEBUG + } else { + Level::TRACE + }, + "Message status not found in db" + ); + PendingOperationStatus::FirstPrepareAttempt + } + }; + + let num_retries = match ctx .origin_db - .retrieve_pending_message_retry_count_by_message_id(&pm.message.id()) + .retrieve_pending_message_retry_count_by_message_id(&message.id()) { - Ok(Some(num_retries)) => { - let next_attempt_after = PendingMessage::calculate_msg_backoff(num_retries) - .map(|dur| Instant::now() + dur); - pm.num_retries = num_retries; - pm.next_attempt_after = next_attempt_after; - } + Ok(Some(num_retries)) => num_retries, r => { - trace!(message_id = ?pm.message.id(), result = ?r, "Failed to read retry count from HyperlaneDB for message.") + trace!(message_id = ?message.id(), result = ?r, "Failed to read retry count from HyperlaneDB for message."); + 0 } + }; + + let mut pm = Self::new(message, ctx, message_status, app_context); + if num_retries > 0 { + let next_attempt_after = + PendingMessage::calculate_msg_backoff(num_retries).map(|dur| Instant::now() + dur); + pm.num_retries = num_retries; + pm.next_attempt_after = next_attempt_after; } pm } diff --git a/rust/main/utils/run-locally/src/invariants/termination_invariants.rs b/rust/main/utils/run-locally/src/invariants/termination_invariants.rs index 50450210c6..65546f6762 100644 --- a/rust/main/utils/run-locally/src/invariants/termination_invariants.rs +++ b/rust/main/utils/run-locally/src/invariants/termination_invariants.rs @@ -90,14 +90,21 @@ pub fn termination_invariants_met( const TX_ID_INDEXING_LOG_MESSAGE: &str = "Found log(s) for tx id"; let relayer_logfile = File::open(log_file_path)?; - let invariant_logs = &[ - STORING_NEW_MESSAGE_LOG_MESSAGE, - LOOKING_FOR_EVENTS_LOG_MESSAGE, - GAS_EXPENDITURE_LOG_MESSAGE, - HYPER_INCOMING_BODY_LOG_MESSAGE, - TX_ID_INDEXING_LOG_MESSAGE, + + let storing_new_msg_line_filter = vec![STORING_NEW_MESSAGE_LOG_MESSAGE]; + let looking_for_events_line_filter = vec![LOOKING_FOR_EVENTS_LOG_MESSAGE]; + let gas_expenditure_line_filter = vec![GAS_EXPENDITURE_LOG_MESSAGE]; + let hyper_incoming_body_line_filter = vec![HYPER_INCOMING_BODY_LOG_MESSAGE]; + let tx_id_indexing_line_filter = vec![TX_ID_INDEXING_LOG_MESSAGE]; + let invariant_logs = vec![ + storing_new_msg_line_filter.clone(), + looking_for_events_line_filter.clone(), + gas_expenditure_line_filter.clone(), + hyper_incoming_body_line_filter.clone(), + tx_id_indexing_line_filter.clone(), ]; let log_counts = get_matching_lines(&relayer_logfile, invariant_logs); + // Zero insertion messages don't reach `submit` stage where gas is spent, so we only expect these logs for the other messages. // TODO: Sometimes we find more logs than expected. This may either mean that gas is deducted twice for the same message due to a bug, // or that submitting the message transaction fails for some messages. Figure out which is the case and convert this check to @@ -105,23 +112,34 @@ pub fn termination_invariants_met( // EDIT: Having had a quick look, it seems like there are some legitimate reverts happening in the confirm step // (`Transaction attempting to process message either reverted or was reorged`) // in which case more gas expenditure logs than messages are expected. - let gas_expenditure_log_count = log_counts.get(GAS_EXPENDITURE_LOG_MESSAGE).unwrap(); + let gas_expenditure_log_count = *log_counts + .get(&gas_expenditure_line_filter) + .expect("Failed to get gas expenditure log count"); assert!( - gas_expenditure_log_count >= &total_messages_expected, + gas_expenditure_log_count >= total_messages_expected, "Didn't record gas payment for all delivered messages. Got {} gas payment logs, expected at least {}", gas_expenditure_log_count, total_messages_expected ); // These tests check that we fixed https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/3915, where some logs would not show up + + let storing_new_msg_log_count = *log_counts + .get(&storing_new_msg_line_filter) + .expect("Failed to get storing new msg log count"); assert!( - log_counts.get(STORING_NEW_MESSAGE_LOG_MESSAGE).unwrap() > &0, + storing_new_msg_log_count > 0, "Didn't find any logs about storing messages in db" ); + let looking_for_events_log_count = *log_counts + .get(&looking_for_events_line_filter) + .expect("Failed to get looking for events log count"); assert!( - log_counts.get(LOOKING_FOR_EVENTS_LOG_MESSAGE).unwrap() > &0, + looking_for_events_log_count > 0, "Didn't find any logs about looking for events in index range" ); - let total_tx_id_log_count = log_counts.get(TX_ID_INDEXING_LOG_MESSAGE).unwrap(); + let total_tx_id_log_count = *log_counts + .get(&tx_id_indexing_line_filter) + .expect("Failed to get tx id indexing log count"); assert!( // there are 3 txid-indexed events: // - relayer: merkle insertion and gas payment @@ -129,13 +147,13 @@ pub fn termination_invariants_met( // some logs are emitted for multiple events, so requiring there to be at least // `config.kathy_messages` logs is a reasonable approximation, since all three of these events // are expected to be logged for each message. - *total_tx_id_log_count as u64 >= config.kathy_messages, + total_tx_id_log_count as u64 >= config.kathy_messages, "Didn't find as many tx id logs as expected. Found {} and expected {}", total_tx_id_log_count, config.kathy_messages ); assert!( - log_counts.get(HYPER_INCOMING_BODY_LOG_MESSAGE).is_none(), + log_counts.get(&hyper_incoming_body_line_filter).is_none(), "Verbose logs not expected at the log level set in e2e" ); diff --git a/rust/main/utils/run-locally/src/main.rs b/rust/main/utils/run-locally/src/main.rs index cdfbc0c7b2..e19fa1564d 100644 --- a/rust/main/utils/run-locally/src/main.rs +++ b/rust/main/utils/run-locally/src/main.rs @@ -33,7 +33,9 @@ use logging::log; pub use metrics::fetch_metric; use once_cell::sync::Lazy; use program::Program; -use tempfile::tempdir; +use relayer::msg::pending_message::RETRIEVED_MESSAGE_LOG; +use tempfile::{tempdir, TempDir}; +use utils::get_matching_lines; use crate::{ config::Config, @@ -179,72 +181,8 @@ fn main() -> ExitCode { .map(|i| concat_path(&rocks_db_dir, format!("validator{i}"))) .collect::>(); - let common_agent_env = Program::default() - .env("RUST_BACKTRACE", "full") - .hyp_env("LOG_FORMAT", "compact") - .hyp_env("LOG_LEVEL", "debug") - .hyp_env("CHAINS_TEST1_INDEX_CHUNK", "1") - .hyp_env("CHAINS_TEST2_INDEX_CHUNK", "1") - .hyp_env("CHAINS_TEST3_INDEX_CHUNK", "1"); - - let multicall_address_string: String = format!("0x{}", hex::encode(MULTICALL_ADDRESS)); - - let relayer_env = common_agent_env - .clone() - .bin(concat_path(AGENT_BIN_PATH, "relayer")) - .hyp_env("CHAINS_TEST1_RPCCONSENSUSTYPE", "fallback") - .hyp_env( - "CHAINS_TEST2_CONNECTION_URLS", - "http://127.0.0.1:8545,http://127.0.0.1:8545,http://127.0.0.1:8545", - ) - .hyp_env( - "CHAINS_TEST1_BATCHCONTRACTADDRESS", - multicall_address_string.clone(), - ) - .hyp_env("CHAINS_TEST1_MAXBATCHSIZE", "5") - // by setting this as a quorum provider we will cause nonce errors when delivering to test2 - // because the message will be sent to the node 3 times. - .hyp_env("CHAINS_TEST2_RPCCONSENSUSTYPE", "quorum") - .hyp_env( - "CHAINS_TEST2_BATCHCONTRACTADDRESS", - multicall_address_string.clone(), - ) - .hyp_env("CHAINS_TEST2_MAXBATCHSIZE", "5") - .hyp_env("CHAINS_TEST3_CONNECTION_URL", "http://127.0.0.1:8545") - .hyp_env( - "CHAINS_TEST3_BATCHCONTRACTADDRESS", - multicall_address_string, - ) - .hyp_env("CHAINS_TEST3_MAXBATCHSIZE", "5") - .hyp_env("METRICSPORT", RELAYER_METRICS_PORT) - .hyp_env("DB", relayer_db.to_str().unwrap()) - .hyp_env("CHAINS_TEST1_SIGNER_KEY", RELAYER_KEYS[0]) - .hyp_env("CHAINS_TEST2_SIGNER_KEY", RELAYER_KEYS[1]) - .hyp_env("CHAINS_SEALEVELTEST1_SIGNER_KEY", RELAYER_KEYS[3]) - .hyp_env("CHAINS_SEALEVELTEST2_SIGNER_KEY", RELAYER_KEYS[4]) - .hyp_env("RELAYCHAINS", "invalidchain,otherinvalid") - .hyp_env("ALLOWLOCALCHECKPOINTSYNCERS", "true") - .hyp_env( - "GASPAYMENTENFORCEMENT", - r#"[{ - "type": "minimum", - "payment": "1" - }]"#, - ) - .arg( - "chains.test1.customRpcUrls", - "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]); - 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 common_agent_env = create_common_agent(); + let relayer_env = create_relayer(&config, &rocks_db_dir); let base_validator_env = common_agent_env .clone() @@ -491,17 +429,19 @@ fn main() -> ExitCode { if !post_startup_invariants(&checkpoints_dirs) { log!("Failure: Post startup invariants are not met"); - return report_test_result(true); + return report_test_result(false); } else { log!("Success: Post startup invariants are met"); } - let mut failure_occurred = false; let starting_relayer_balance: f64 = agent_balance_sum(9092).unwrap(); - while !SHUTDOWN.load(Ordering::Relaxed) { - if config.ci_mode { - // for CI we have to look for the end condition. - if termination_invariants_met( + + // wait for CI invariants to pass + let mut test_passed = wait_for_condition( + &config, + loop_start, + || { + termination_invariants_met( &config, starting_relayer_balance, solana_paths @@ -510,49 +450,225 @@ fn main() -> ExitCode { .as_deref(), solana_config_path.as_deref(), ) - .unwrap_or(false) - { - // end condition reached successfully - break; - } else if (Instant::now() - loop_start).as_secs() > config.ci_mode_timeout { - // we ran out of time - log!("CI timeout reached before queues emptied"); - failure_occurred = true; - break; - } - } - - // verify long-running tasks are still running - for (name, (child, _)) in state.agents.iter_mut() { - if let Some(status) = child.try_wait().unwrap() { - if !status.success() { - log!( - "Child process {} exited unexpectedly, with code {}. Shutting down", - name, - status.code().unwrap() - ); - failure_occurred = true; - SHUTDOWN.store(true, Ordering::Relaxed); - break; - } - } - } + }, + || !SHUTDOWN.load(Ordering::Relaxed), + || long_running_processes_exited_check(&mut state), + ); - sleep(Duration::from_secs(5)); + if !test_passed { + log!("Failure occurred during E2E"); + return report_test_result(test_passed); } + + // Here we want to restart the relayer and validate + // its restart behaviour. + restart_relayer(&config, &mut state, &rocks_db_dir); + + // give relayer a chance to fully restart. + sleep(Duration::from_secs(20)); + + let loop_start = Instant::now(); + // wait for Relayer restart invariants to pass + test_passed = wait_for_condition( + &config, + loop_start, + relayer_restart_invariants_met, + || !SHUTDOWN.load(Ordering::Relaxed), + || long_running_processes_exited_check(&mut state), + ); + // test retry request let resp = server::run_retry_request().expect("Failed to process retry request"); assert!(resp.matched > 0); - report_test_result(failure_occurred) + report_test_result(test_passed) } -fn report_test_result(failure_occurred: bool) -> ExitCode { - if failure_occurred { - log!("E2E tests failed"); - ExitCode::FAILURE +fn create_common_agent() -> Program { + Program::default() + .env("RUST_BACKTRACE", "full") + .hyp_env("LOG_FORMAT", "compact") + .hyp_env("LOG_LEVEL", "debug") + .hyp_env("CHAINS_TEST1_INDEX_CHUNK", "1") + .hyp_env("CHAINS_TEST2_INDEX_CHUNK", "1") + .hyp_env("CHAINS_TEST3_INDEX_CHUNK", "1") +} + +fn create_relayer(config: &Config, rocks_db_dir: &TempDir) -> Program { + let relayer_db = concat_path(rocks_db_dir, "relayer"); + + let common_agent_env = create_common_agent(); + + let multicall_address_string: String = format!("0x{}", hex::encode(MULTICALL_ADDRESS)); + + let relayer_env = common_agent_env + .clone() + .bin(concat_path(AGENT_BIN_PATH, "relayer")) + .hyp_env("CHAINS_TEST1_RPCCONSENSUSTYPE", "fallback") + .hyp_env( + "CHAINS_TEST2_CONNECTION_URLS", + "http://127.0.0.1:8545,http://127.0.0.1:8545,http://127.0.0.1:8545", + ) + .hyp_env( + "CHAINS_TEST1_BATCHCONTRACTADDRESS", + multicall_address_string.clone(), + ) + .hyp_env("CHAINS_TEST1_MAXBATCHSIZE", "5") + // by setting this as a quorum provider we will cause nonce errors when delivering to test2 + // because the message will be sent to the node 3 times. + .hyp_env("CHAINS_TEST2_RPCCONSENSUSTYPE", "quorum") + .hyp_env( + "CHAINS_TEST2_BATCHCONTRACTADDRESS", + multicall_address_string.clone(), + ) + .hyp_env("CHAINS_TEST2_MAXBATCHSIZE", "5") + .hyp_env("CHAINS_TEST3_CONNECTION_URL", "http://127.0.0.1:8545") + .hyp_env( + "CHAINS_TEST3_BATCHCONTRACTADDRESS", + multicall_address_string, + ) + .hyp_env("CHAINS_TEST3_MAXBATCHSIZE", "5") + .hyp_env("METRICSPORT", RELAYER_METRICS_PORT) + .hyp_env("DB", relayer_db.to_str().unwrap()) + .hyp_env("CHAINS_TEST1_SIGNER_KEY", RELAYER_KEYS[0]) + .hyp_env("CHAINS_TEST2_SIGNER_KEY", RELAYER_KEYS[1]) + .hyp_env("CHAINS_SEALEVELTEST1_SIGNER_KEY", RELAYER_KEYS[3]) + .hyp_env("CHAINS_SEALEVELTEST2_SIGNER_KEY", RELAYER_KEYS[4]) + .hyp_env("RELAYCHAINS", "invalidchain,otherinvalid") + .hyp_env("ALLOWLOCALCHECKPOINTSYNCERS", "true") + .hyp_env( + "GASPAYMENTENFORCEMENT", + r#"[{ + "type": "minimum", + "payment": "1" + }]"#, + ) + .arg( + "chains.test1.customRpcUrls", + "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]); + if config.sealevel_enabled { + relayer_env.arg( + "relayChains", + "test1,test2,test3,sealeveltest1,sealeveltest2", + ) } else { + relayer_env.arg("relayChains", "test1,test2,test3") + } +} + +/// Kills relayer in State and respawns the relayer again +fn restart_relayer(config: &Config, state: &mut State, rocks_db_dir: &TempDir) { + log!("Stopping relayer..."); + let (child, _) = state.agents.get_mut("RLY").expect("No relayer agent found"); + child.kill().expect("Failed to stop relayer"); + + log!("Restarting relayer..."); + let relayer_env = create_relayer(config, rocks_db_dir); + state.push_agent(relayer_env.spawn("RLY", Some(&AGENT_LOGGING_DIR))); + log!("Restarted relayer..."); +} + +/// Check relayer restart behaviour is correct. +/// So far, we only check if undelivered messages' statuses +/// are correctly retrieved from the database +fn relayer_restart_invariants_met() -> eyre::Result { + let log_file_path = AGENT_LOGGING_DIR.join("RLY-output.log"); + let relayer_logfile = File::open(log_file_path).unwrap(); + + let line_filters = vec![RETRIEVED_MESSAGE_LOG, "CouldNotFetchMetadata"]; + + log!("Checking message statuses were retrieved from logs..."); + let matched_logs = get_matching_lines(&relayer_logfile, vec![line_filters.clone()]); + + let no_metadata_message_count = *matched_logs + .get(&line_filters) + .expect("Failed to get matched message count"); + // These messages are never inserted into the merkle tree. + // So these messages will never be deliverable and will always + // be in a CouldNotFetchMetadata state. + // When the relayer restarts, these messages' statuses should be + // retrieved from the database with CouldNotFetchMetadata status. + if no_metadata_message_count < ZERO_MERKLE_INSERTION_KATHY_MESSAGES { + log!( + "No metadata message count is {}, expected {}", + no_metadata_message_count, + ZERO_MERKLE_INSERTION_KATHY_MESSAGES + ); + return Ok(false); + } + assert_eq!( + no_metadata_message_count, + ZERO_MERKLE_INSERTION_KATHY_MESSAGES + ); + Ok(true) +} + +fn wait_for_condition( + config: &Config, + start_time: Instant, + condition_fn: F1, + loop_invariant_fn: F2, + mut shutdown_criteria_fn: F3, +) -> bool +where + F1: Fn() -> eyre::Result, + F2: Fn() -> bool, + F3: FnMut() -> bool, +{ + let loop_check_interval = Duration::from_secs(5); + while loop_invariant_fn() { + sleep(loop_check_interval); + if !config.ci_mode { + continue; + } + if condition_fn().unwrap_or(false) { + // end condition reached successfully + break; + } + if check_ci_timed_out(config.ci_mode_timeout, start_time) { + // we ran out of time + log!("CI timeout reached before invariants were met"); + return false; + } + if shutdown_criteria_fn() { + SHUTDOWN.store(true, Ordering::Relaxed); + return false; + } + } + true +} + +/// check if CI has timed out based on config +fn check_ci_timed_out(timeout_secs: u64, start_time: Instant) -> bool { + (Instant::now() - start_time).as_secs() > timeout_secs +} + +/// verify long-running tasks are still running +fn long_running_processes_exited_check(state: &mut State) -> bool { + for (name, (child, _)) in state.agents.iter_mut() { + if let Some(status) = child.try_wait().unwrap() { + if !status.success() { + log!( + "Child process {} exited unexpectedly, with code {}. Shutting down", + name, + status.code().unwrap() + ); + return true; + } + } + } + false +} + +fn report_test_result(passed: bool) -> ExitCode { + if passed { log!("E2E tests passed"); ExitCode::SUCCESS + } else { + log!("E2E tests failed"); + ExitCode::FAILURE } } diff --git a/rust/main/utils/run-locally/src/utils.rs b/rust/main/utils/run-locally/src/utils.rs index 5e5dd6a126..ebb5a70245 100644 --- a/rust/main/utils/run-locally/src/utils.rs +++ b/rust/main/utils/run-locally/src/utils.rs @@ -119,15 +119,28 @@ pub fn stop_child(child: &mut Child) { }; } -pub fn get_matching_lines(file: &File, search_strings: &[&str]) -> HashMap { +/// Given a Vec>, +/// for each Vec<&str>, count how many lines in the file +/// matches all the &str in that Vec. +/// Store this count in a hashmap where the key is the vector +/// Vec<&str> +/// and return this hashmap. +pub fn get_matching_lines<'a>( + file: &File, + search_strings: Vec>, +) -> HashMap, u32> { let reader = io::BufReader::new(file); let mut matches = HashMap::new(); let mut lines = reader.lines(); while let Some(Ok(line)) = lines.next() { - search_strings.iter().for_each(|search_string| { - if line.contains(search_string) { - let count = matches.entry(search_string.to_string()).or_insert(0); + search_strings.iter().for_each(|search_string_vec| { + if search_string_vec + .iter() + .map(|search_string| line.contains(search_string)) + .all(|x| x) + { + let count = matches.entry(search_string_vec.clone()).or_insert(0); *count += 1; } }); From 6bec48d028a94adb3fa918ad4613f5714e879b8f Mon Sep 17 00:00:00 2001 From: Lee <6251863+ltyu@users.noreply.github.com> Date: Wed, 29 Jan 2025 12:30:33 -0500 Subject: [PATCH 03/11] chore: Clean up getters (#5316) ### Description This PR intends to clean up getters to create configs that do not include unused fields. For example, the `ownerOverrides` with all the extra fields: https://github.com/hyperlane-xyz/hyperlane-registry/blob/83cd45dba92a56d0b418b5941bb88d4fe0b04c65/deployments/warp_routes/ETH/ethereum-viction-deploy.yaml#L1-L12 The resulting configs https://github.com/hyperlane-xyz/hyperlane-registry/pull/530/files ### Related issues ### Backward compatibility Yes ### Testing --- ...etArbitrumEthereumZircuitAmphrETHWarpConfig.ts | 15 ++++++++++++--- .../getEthereumBscLumiaLUMIAWarpConfig.ts | 14 +++++++++----- .../getEthereumFlowCbBTCWarpConfig.ts | 13 ++++++++----- .../getEthereumFormUSDCWarpConfig.ts | 11 ++++++++--- .../getEthereumFormUSDTWarpConfig.ts | 11 ++++++++--- .../getEthereumSeiFastUSDWarpConfig.ts | 7 ++++--- .../getEthereumSeiPumpBTCWarpConfig.ts | 4 ++-- 7 files changed, 51 insertions(+), 24 deletions(-) diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getArbitrumEthereumZircuitAmphrETHWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getArbitrumEthereumZircuitAmphrETHWarpConfig.ts index 866e3bb9bc..14205f73e1 100644 --- a/typescript/infra/config/environments/mainnet3/warp/configGetters/getArbitrumEthereumZircuitAmphrETHWarpConfig.ts +++ b/typescript/infra/config/environments/mainnet3/warp/configGetters/getArbitrumEthereumZircuitAmphrETHWarpConfig.ts @@ -24,14 +24,20 @@ export const getArbitrumEthereumZircuitAmphrETHWarpConfig = async ( ): Promise> => { const arbitrum: HypTokenRouterConfig = { ...routerConfig.arbitrum, - ...getOwnerConfigForAddress(arbitrumOwner), + owner: arbitrumOwner, + proxyAdmin: { + owner: arbitrumOwner, + }, type: TokenType.synthetic, interchainSecurityModule: ethers.constants.AddressZero, }; const ethereum: HypTokenRouterConfig = { ...routerConfig.ethereum, - ...getOwnerConfigForAddress(ethereumOwner), + owner: ethereumOwner, + proxyAdmin: { + owner: ethereumOwner, + }, type: TokenType.collateral, token: tokens.ethereum.amphrETH, interchainSecurityModule: ethers.constants.AddressZero, @@ -39,7 +45,10 @@ export const getArbitrumEthereumZircuitAmphrETHWarpConfig = async ( const zircuit: HypTokenRouterConfig = { ...routerConfig.zircuit, - ...getOwnerConfigForAddress(zircuitOwner), + owner: zircuitOwner, + proxyAdmin: { + owner: zircuitOwner, + }, type: TokenType.synthetic, interchainSecurityModule: ethers.constants.AddressZero, }; diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumBscLumiaLUMIAWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumBscLumiaLUMIAWarpConfig.ts index 0551b09bad..c38160c5a0 100644 --- a/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumBscLumiaLUMIAWarpConfig.ts +++ b/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumBscLumiaLUMIAWarpConfig.ts @@ -5,12 +5,10 @@ import { TokenType, } from '@hyperlane-xyz/sdk'; -import { getOwnerConfigForAddress } from '../../../../../src/config/environment.js'; import { RouterConfigWithoutOwner } from '../../../../../src/config/warp.js'; // Lumia Team const owner = '0x8bBA07Ddc72455b55530C17e6f6223EF6E156863'; -const ownerConfig = getOwnerConfigForAddress(owner); export const getEthereumBscLUMIAWarpConfig = async ( routerConfig: ChainMap, @@ -18,20 +16,26 @@ export const getEthereumBscLUMIAWarpConfig = async ( ): Promise> => { const ethereum: HypTokenRouterConfig = { ...routerConfig.ethereum, - ...ownerConfig, + owner, + proxyAdmin: { + owner, + }, type: TokenType.collateral, token: '0xD9343a049D5DBd89CD19DC6BcA8c48fB3a0a42a7', }; const bsc: HypTokenRouterConfig = { ...routerConfig.bsc, - ...ownerConfig, + owner, + proxyAdmin: { + owner, + }, type: TokenType.synthetic, }; const lumia: HypTokenRouterConfig = { ...routerConfig.lumia, - ...ownerConfig, + owner, type: TokenType.native, // As this has been removed from the registry in https://github.com/hyperlane-xyz/hyperlane-registry/pull/348, // we must specify this explicitly. diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumFlowCbBTCWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumFlowCbBTCWarpConfig.ts index 54acc7fbd4..e96c5625a3 100644 --- a/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumFlowCbBTCWarpConfig.ts +++ b/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumFlowCbBTCWarpConfig.ts @@ -7,7 +7,6 @@ import { TokenType, } from '@hyperlane-xyz/sdk'; -import { getOwnerConfigForAddress } from '../../../../../src/config/environment.js'; import { RouterConfigWithoutOwner, tokens, @@ -15,11 +14,9 @@ import { // Flow team Safe const ethereumOwner = '0x58C3FB862a4F5f038C24F8506BE378e9415c5B6C'; -const ethereumOwnerConfig = getOwnerConfigForAddress(ethereumOwner); // Flow team Safe const flowOwner = '0xa507DFccA02727B46cBdC600C57E89b2b55E5330'; -const flowOwnerConfig = getOwnerConfigForAddress(flowOwner); export const getEthereumFlowCbBTCWarpConfig = async ( routerConfig: ChainMap, @@ -27,7 +24,10 @@ export const getEthereumFlowCbBTCWarpConfig = async ( ): Promise> => { const ethereum: HypTokenRouterConfig = { ...routerConfig.ethereum, - ...ethereumOwnerConfig, + owner: ethereumOwner, + proxyAdmin: { + owner: ethereumOwner, + }, type: TokenType.collateral, token: tokens.ethereum.cbBTC, interchainSecurityModule: ethers.constants.AddressZero, @@ -35,7 +35,10 @@ export const getEthereumFlowCbBTCWarpConfig = async ( const flowmainnet: HypTokenRouterConfig = { ...routerConfig.flowmainnet, - ...flowOwnerConfig, + owner: flowOwner, + proxyAdmin: { + owner: flowOwner, + }, type: TokenType.synthetic, interchainSecurityModule: ethers.constants.AddressZero, }; diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumFormUSDCWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumFormUSDCWarpConfig.ts index 2118a3860f..9c4482b714 100644 --- a/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumFormUSDCWarpConfig.ts +++ b/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumFormUSDCWarpConfig.ts @@ -3,7 +3,6 @@ import { ethers } from 'ethers'; import { ChainMap, HypTokenRouterConfig, TokenType } from '@hyperlane-xyz/sdk'; import { Address } from '@hyperlane-xyz/utils'; -import { getOwnerConfigForAddress } from '../../../../../src/config/environment.js'; import { RouterConfigWithoutOwner, tokens, @@ -20,7 +19,10 @@ export const getEthereumFormUSDCWarpConfig = async ( ): Promise> => { const ethereum: HypTokenRouterConfig = { ...routerConfig.ethereum, - ...getOwnerConfigForAddress(safeOwners.ethereum), + owner: safeOwners.ethereum, + proxyAdmin: { + owner: safeOwners.ethereum, + }, type: TokenType.collateral, token: tokens.ethereum.USDC, interchainSecurityModule: ethers.constants.AddressZero, @@ -30,7 +32,10 @@ export const getEthereumFormUSDCWarpConfig = async ( // MasterMinter 0x9Dec8Dfafcce2d45E8FF8C7792DB1D704AB1dc9D const form: HypTokenRouterConfig = { ...routerConfig.form, - ...getOwnerConfigForAddress(safeOwners.form), + owner: safeOwners.form, + proxyAdmin: { + owner: safeOwners.form, + }, type: TokenType.collateralFiat, token: '0xFBf489bb4783D4B1B2e7D07ba39873Fb8068507D', interchainSecurityModule: ethers.constants.AddressZero, diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumFormUSDTWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumFormUSDTWarpConfig.ts index aa8bd4013a..0f2b6c3c28 100644 --- a/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumFormUSDTWarpConfig.ts +++ b/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumFormUSDTWarpConfig.ts @@ -3,7 +3,6 @@ import { ethers } from 'ethers'; import { ChainMap, HypTokenRouterConfig, TokenType } from '@hyperlane-xyz/sdk'; import { Address } from '@hyperlane-xyz/utils'; -import { getOwnerConfigForAddress } from '../../../../../src/config/environment.js'; import { RouterConfigWithoutOwner, tokens, @@ -20,7 +19,10 @@ export const getEthereumFormUSDTWarpConfig = async ( ): Promise> => { const ethereum: HypTokenRouterConfig = { ...routerConfig.ethereum, - ...getOwnerConfigForAddress(safeOwners.ethereum), + owner: safeOwners.ethereum, + proxyAdmin: { + owner: safeOwners.ethereum, + }, type: TokenType.collateral, token: tokens.ethereum.USDT, interchainSecurityModule: ethers.constants.AddressZero, @@ -28,7 +30,10 @@ export const getEthereumFormUSDTWarpConfig = async ( const form: HypTokenRouterConfig = { ...routerConfig.form, - ...getOwnerConfigForAddress(safeOwners.form), + owner: safeOwners.form, + proxyAdmin: { + owner: safeOwners.form, + }, type: TokenType.synthetic, interchainSecurityModule: ethers.constants.AddressZero, }; diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumSeiFastUSDWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumSeiFastUSDWarpConfig.ts index cb90ba42a8..36b01eb6eb 100644 --- a/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumSeiFastUSDWarpConfig.ts +++ b/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumSeiFastUSDWarpConfig.ts @@ -7,7 +7,6 @@ import { TokenType, } from '@hyperlane-xyz/sdk'; -import { getOwnerConfigForAddress } from '../../../../../src/config/environment.js'; import { RouterConfigWithoutOwner, tokens, @@ -16,7 +15,6 @@ import { // Elixir const owner = '0x00000000F51340906F767C6999Fe512b1275955C'; const elixirSafe = '0x738744237b7fd97af670d9ddf54390c24263cea8'; -const ownerConfig = getOwnerConfigForAddress(owner); export const getEthereumSeiFastUSDWarpConfig = async ( routerConfig: ChainMap, @@ -24,7 +22,10 @@ export const getEthereumSeiFastUSDWarpConfig = async ( ): Promise> => { const sei: HypTokenRouterConfig = { ...routerConfig.viction, - ...ownerConfig, + owner, + proxyAdmin: { + owner, + }, type: TokenType.XERC20, name: 'fastUSD', symbol: 'fastUSD', diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumSeiPumpBTCWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumSeiPumpBTCWarpConfig.ts index 2f65c69323..c321f2efcc 100644 --- a/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumSeiPumpBTCWarpConfig.ts +++ b/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumSeiPumpBTCWarpConfig.ts @@ -23,7 +23,7 @@ export const getEthereumSeiPumpBTCWarpConfig = async ( ): Promise> => { const ethereum: HypTokenRouterConfig = { ...routerConfig.ethereum, - ...getOwnerConfigForAddress(ethereumOwner), + owner: ethereumOwner, proxyAdmin: { // Address explicitly specified as move away from the AW proxy admin address: '0x64d4ba42f033927ca3babbbebaa11ac8caed9472', @@ -36,7 +36,7 @@ export const getEthereumSeiPumpBTCWarpConfig = async ( const sei: HypTokenRouterConfig = { ...routerConfig.sei, - ...getOwnerConfigForAddress(seiOwner), + owner: seiOwner, proxyAdmin: { // Address explicitly specified as move away from the AW proxy admin address: '0x932a0a357CbE9a06c0FCec8C56335DA162c5D071', From 68c2f86b7a8b1fb7db988942dcd552656a3e3007 Mon Sep 17 00:00:00 2001 From: Lee <6251863+ltyu@users.noreply.github.com> Date: Wed, 29 Jan 2025 15:51:59 -0500 Subject: [PATCH 04/11] feat: Add warp config test (#5304) ### Description This PR adds warp config unit tests to compares the Registry's configs to the configs generated by the Checkers. The goal of this test is to ensure syncing between the programatic configs (e.g. Renzo) to whats checked into the Registry. ### Backward compatibility Yes ### Testing Manual Fixes: https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/5336 --- typescript/cli/package.json | 2 +- typescript/helloworld/package.json | 2 +- typescript/infra/config/registry.ts | 10 +++++ typescript/infra/package.json | 2 +- typescript/infra/test/warp-configs.test.ts | 52 ++++++++++++++++++++++ typescript/widgets/package.json | 2 +- yarn.lock | 16 +++---- 7 files changed, 74 insertions(+), 12 deletions(-) create mode 100644 typescript/infra/test/warp-configs.test.ts diff --git a/typescript/cli/package.json b/typescript/cli/package.json index 27be825f14..30516c94a3 100644 --- a/typescript/cli/package.json +++ b/typescript/cli/package.json @@ -5,7 +5,7 @@ "dependencies": { "@aws-sdk/client-kms": "^3.577.0", "@aws-sdk/client-s3": "^3.577.0", - "@hyperlane-xyz/registry": "7.1.0", + "@hyperlane-xyz/registry": "7.4.0", "@hyperlane-xyz/sdk": "8.5.0", "@hyperlane-xyz/utils": "8.5.0", "@inquirer/core": "9.0.10", diff --git a/typescript/helloworld/package.json b/typescript/helloworld/package.json index f70d6f6d84..1ed81a27ac 100644 --- a/typescript/helloworld/package.json +++ b/typescript/helloworld/package.json @@ -4,7 +4,7 @@ "version": "8.5.0", "dependencies": { "@hyperlane-xyz/core": "5.11.1", - "@hyperlane-xyz/registry": "7.1.0", + "@hyperlane-xyz/registry": "7.4.0", "@hyperlane-xyz/sdk": "8.5.0", "@openzeppelin/contracts-upgradeable": "^4.9.3", "ethers": "^5.7.2" diff --git a/typescript/infra/config/registry.ts b/typescript/infra/config/registry.ts index 526c23c14b..33984a6c8d 100644 --- a/typescript/infra/config/registry.ts +++ b/typescript/infra/config/registry.ts @@ -3,6 +3,7 @@ import { fileURLToPath } from 'url'; import { ChainAddresses, + GithubRegistry, MergedRegistry, PartialRegistry, warpConfigToWarpAddresses, @@ -33,6 +34,8 @@ const DEFAULT_REGISTRY_URI = join( 'hyperlane-registry', ); +const REGISTRY_PROXY = 'https://proxy.hyperlane.xyz'; + // A global Registry singleton // All uses of chain metadata or chain address artifacts should go through this registry. let registry: FileSystemRegistry; @@ -61,6 +64,13 @@ export function getRegistry(): FileSystemRegistry { return registry; } +export function getGithubRegistry(): GithubRegistry { + return new GithubRegistry({ + proxyUrl: REGISTRY_PROXY, + logger: rootLogger.child({ module: 'infra-registry' }), + }); +} + export function getChains(): ChainName[] { return getRegistry().getChains(); } diff --git a/typescript/infra/package.json b/typescript/infra/package.json index ba2cc9a219..a445ee7359 100644 --- a/typescript/infra/package.json +++ b/typescript/infra/package.json @@ -14,7 +14,7 @@ "@ethersproject/providers": "*", "@google-cloud/secret-manager": "^5.5.0", "@hyperlane-xyz/helloworld": "8.5.0", - "@hyperlane-xyz/registry": "7.1.0", + "@hyperlane-xyz/registry": "7.4.0", "@hyperlane-xyz/sdk": "8.5.0", "@hyperlane-xyz/utils": "8.5.0", "@inquirer/prompts": "3.3.2", diff --git a/typescript/infra/test/warp-configs.test.ts b/typescript/infra/test/warp-configs.test.ts new file mode 100644 index 0000000000..e875ec0d3c --- /dev/null +++ b/typescript/infra/test/warp-configs.test.ts @@ -0,0 +1,52 @@ +import { expect } from 'chai'; + +import { MultiProvider } from '@hyperlane-xyz/sdk'; +import { diffObjMerge } from '@hyperlane-xyz/utils'; + +import { getGithubRegistry } from '../config/registry.js'; +import { getWarpConfig, warpConfigGetterMap } from '../config/warp.js'; +import { + getEnvironmentConfig, + getHyperlaneCore, +} from '../scripts/core-utils.js'; + +const DEFAULT_TIMEOUT = 60000; +describe('Warp Configs', async function () { + this.timeout(DEFAULT_TIMEOUT); + const ENV = 'mainnet3'; + const warpIdsToCheck = Object.keys(warpConfigGetterMap); + let multiProvider: MultiProvider; + + before(async () => { + multiProvider = (await getHyperlaneCore(ENV)).multiProvider; + }); + + const envConfig = getEnvironmentConfig(ENV); + + for (const warpRouteId of warpIdsToCheck) { + it(`should match Github Registry configs for ${warpRouteId}`, async () => { + const warpConfig = await getWarpConfig( + multiProvider, + envConfig, + warpRouteId, + ); + const githubRegistry = getGithubRegistry(); + const configsFromGithub = await githubRegistry.getWarpDeployConfig( + warpRouteId, + ); + const { mergedObject, isInvalid } = diffObjMerge( + warpConfig, + configsFromGithub!, + ); + + if (isInvalid) { + console.log('Differences', JSON.stringify(mergedObject, null, 2)); + } + + expect( + isInvalid, + `Registry config does not match Getter for ${warpRouteId}`, + ).to.be.false; + }); + } +}); diff --git a/typescript/widgets/package.json b/typescript/widgets/package.json index 15a4ca1326..c7e2c4acec 100644 --- a/typescript/widgets/package.json +++ b/typescript/widgets/package.json @@ -27,7 +27,7 @@ "@emotion/react": "^11.13.3", "@emotion/styled": "^11.13.0", "@eslint/js": "^9.15.0", - "@hyperlane-xyz/registry": "7.1.0", + "@hyperlane-xyz/registry": "7.4.0", "@storybook/addon-essentials": "^7.6.14", "@storybook/addon-interactions": "^7.6.14", "@storybook/addon-links": "^7.6.14", diff --git a/yarn.lock b/yarn.lock index 75ff7cba40..5728616979 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7320,7 +7320,7 @@ __metadata: "@eslint/js": "npm:^9.15.0" "@ethersproject/abi": "npm:*" "@ethersproject/providers": "npm:*" - "@hyperlane-xyz/registry": "npm:7.1.0" + "@hyperlane-xyz/registry": "npm:7.4.0" "@hyperlane-xyz/sdk": "npm:8.5.0" "@hyperlane-xyz/utils": "npm:8.5.0" "@inquirer/core": "npm:9.0.10" @@ -7424,7 +7424,7 @@ __metadata: dependencies: "@eslint/js": "npm:^9.15.0" "@hyperlane-xyz/core": "npm:5.11.1" - "@hyperlane-xyz/registry": "npm:7.1.0" + "@hyperlane-xyz/registry": "npm:7.4.0" "@hyperlane-xyz/sdk": "npm:8.5.0" "@nomiclabs/hardhat-ethers": "npm:^2.2.3" "@nomiclabs/hardhat-waffle": "npm:^2.0.6" @@ -7475,7 +7475,7 @@ __metadata: "@ethersproject/providers": "npm:*" "@google-cloud/secret-manager": "npm:^5.5.0" "@hyperlane-xyz/helloworld": "npm:8.5.0" - "@hyperlane-xyz/registry": "npm:7.1.0" + "@hyperlane-xyz/registry": "npm:7.4.0" "@hyperlane-xyz/sdk": "npm:8.5.0" "@hyperlane-xyz/utils": "npm:8.5.0" "@inquirer/prompts": "npm:3.3.2" @@ -7537,13 +7537,13 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/registry@npm:7.1.0": - version: 7.1.0 - resolution: "@hyperlane-xyz/registry@npm:7.1.0" +"@hyperlane-xyz/registry@npm:7.4.0": + version: 7.4.0 + resolution: "@hyperlane-xyz/registry@npm:7.4.0" dependencies: yaml: "npm:2.4.5" zod: "npm:^3.21.2" - checksum: 10/94b594ecd5734bc564e1a9ad220f4e5c9f04a98cd768cd4472afbaffe91009ae0680b8c4ffb01c13dbe913030dd730b22d5a73d7bfc88d6405df1e9f842ef939 + checksum: 10/a7843cf18d69b595f2c7320f6165632685468a893d15c8e2456d1a41359a1c28d924f50931adbd7111494cae64a51efaa8e5ec102a581171bf051566fc872e00 languageName: node linkType: hard @@ -7642,7 +7642,7 @@ __metadata: "@emotion/styled": "npm:^11.13.0" "@eslint/js": "npm:^9.15.0" "@headlessui/react": "npm:^2.1.8" - "@hyperlane-xyz/registry": "npm:7.1.0" + "@hyperlane-xyz/registry": "npm:7.4.0" "@hyperlane-xyz/sdk": "npm:8.5.0" "@hyperlane-xyz/utils": "npm:8.5.0" "@interchain-ui/react": "npm:^1.23.28" From 57d22cf7555502975e07d9b54d2f2940dbc6f43b Mon Sep 17 00:00:00 2001 From: Mohammed Hussan Date: Thu, 30 Jan 2025 15:56:03 +0000 Subject: [PATCH 05/11] feat(balances): Support automatic desired balances and alert thresholds based on daily burn approximations (#5270) ### Description - calculates daily burn based on recent relayer usage, min dollar value targets and a minimum number of tx, check this into monorepo as `dailyBurn.json` ``` yarn tsx ./scripts/funding/calculate-relayer-daily-burn.ts ``` - Support reading alert thresholds for `lowUrgencyKeyFunderBalance`, `highUrgencyRelayerBalance` and writing them to checked in config ``` yarn tsx ./scripts/funding/read-alert-thresholds.ts -e mainnet3 --alertType highUrgencyRelayerBalance --write ``` - Support updating checked in config based on the dailyBurn and multipliers ![image](https://github.com/user-attachments/assets/cf11c4e9-1168-468a-a64c-ce48c8b058da) - Support writing alert queries to grafana ![image](https://github.com/user-attachments/assets/d810fb35-90a5-43d2-8aaa-50b33e3547a4) Note, the configs are not in use at the moment and will be used as we roll this out ### Related issues ### Backward compatibility ### Testing Manual --- .../mainnet3/balances/dailyRelayerBurn.json | 124 +++++++++++ .../balances/desiredRelayerBalances.json | 124 +++++++++++ .../balances/highUrgencyRelayerBalance.json | 122 +++++++++++ .../balances/lowUrgencyKeyFunderBalance.json | 122 +++++++++++ typescript/infra/package.json | 1 + typescript/infra/scripts/agent-utils.ts | 32 +++ .../funding/calculate-relayer-daily-burn.ts | 137 ++++++++++++ .../scripts/funding/read-alert-thresholds.ts | 41 ++++ .../update-balance-threshold-config.ts | 111 ++++++++++ .../infra/scripts/funding/write-alert.ts | 72 ++++++ typescript/infra/src/agents/key-utils.ts | 3 +- .../config/funding/alert-query-templates.ts | 56 +++++ .../infra/src/config/funding/balances.ts | 45 ++++ .../infra/src/config/funding/grafanaAlerts.ts | 166 ++++++++++++++ typescript/infra/src/funding/grafana.ts | 207 ++++++++++++++++++ yarn.lock | 8 + 16 files changed, 1369 insertions(+), 2 deletions(-) create mode 100644 typescript/infra/config/environments/mainnet3/balances/dailyRelayerBurn.json create mode 100644 typescript/infra/config/environments/mainnet3/balances/desiredRelayerBalances.json create mode 100644 typescript/infra/config/environments/mainnet3/balances/highUrgencyRelayerBalance.json create mode 100644 typescript/infra/config/environments/mainnet3/balances/lowUrgencyKeyFunderBalance.json create mode 100644 typescript/infra/scripts/funding/calculate-relayer-daily-burn.ts create mode 100644 typescript/infra/scripts/funding/read-alert-thresholds.ts create mode 100644 typescript/infra/scripts/funding/update-balance-threshold-config.ts create mode 100644 typescript/infra/scripts/funding/write-alert.ts create mode 100644 typescript/infra/src/config/funding/alert-query-templates.ts create mode 100644 typescript/infra/src/config/funding/balances.ts create mode 100644 typescript/infra/src/config/funding/grafanaAlerts.ts create mode 100644 typescript/infra/src/funding/grafana.ts diff --git a/typescript/infra/config/environments/mainnet3/balances/dailyRelayerBurn.json b/typescript/infra/config/environments/mainnet3/balances/dailyRelayerBurn.json new file mode 100644 index 0000000000..38104dd6c2 --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/balances/dailyRelayerBurn.json @@ -0,0 +1,124 @@ +{ + "abstract": 0.00291, + "ancient8": 0.00373, + "alephzeroevmmainnet": 13.8, + "apechain": 3.56, + "appchain": 0.0236, + "arbitrum": 0.0402, + "arbitrumnova": 0.00102, + "artela": 191, + "arthera": 108, + "astar": 65.9, + "astarzkevm": 0.00369, + "aurora": 0.00102, + "flame": 1.95, + "avalanche": 0.0943, + "b3": 0.00427, + "base": 0.0712, + "bitlayer": 0.00202, + "blast": 0.00591, + "bob": 0.00352, + "boba": 0.00351, + "bsc": 0.167, + "bsquared": 0.00362, + "celo": 6.04, + "cheesechain": 2670, + "chilizmainnet": 100, + "conflux": 21.5, + "conwai": 889, + "coredao": 4.56, + "corn": 0.0000316, + "cyber": 0.00443, + "degenchain": 481, + "dogechain": 10.5, + "duckchain": 0.643, + "eclipsemainnet": 0.025, + "endurance": 2.23, + "ethereum": 0.556, + "everclear": 0.00626, + "evmos": 224, + "fantom": 6.61, + "flare": 133, + "flowmainnet": 5.13, + "form": 0.00355, + "fraxtal": 0.016, + "fusemainnet": 126, + "glue": 8.4, + "gnosis": 3.12, + "gravity": 122, + "guru": 280, + "harmony": 171, + "hemi": 0.00102, + "immutablezkevmmainnet": 4.14, + "inevm": 0.171, + "ink": 0.0113, + "injective": 0.171, + "kaia": 16.8, + "kroma": 0.00341, + "linea": 0.0782, + "lisk": 0.00781, + "lukso": 1.97, + "lumia": 3.21, + "lumiaprism": 4.09, + "mantapacific": 0.00371, + "mantle": 13.9, + "matchain": 0.0048, + "merlin": 0.000318, + "metal": 0.0102, + "metis": 0.1, + "mint": 0.00462, + "mode": 0.0135, + "molten": 9.51, + "moonbeam": 24.1, + "morph": 0.283, + "nero": 3.13, + "neutron": 11.8, + "oortmainnet": 36.7, + "optimism": 0.0354, + "orderly": 0.00471, + "osmosis": 8.28, + "polygon": 16.9, + "polygonzkevm": 0.0447, + "polynomialfi": 0.00436, + "prom": 2.92, + "proofofplay": 0.00102, + "rarichain": 0.00691, + "real": 0.00157, + "redstone": 0.00344, + "rivalz": 0.00102, + "rootstockmainnet": 0.00241, + "sanko": 0.158, + "scroll": 0.0169, + "sei": 10.4, + "shibarium": 9.34, + "snaxchain": 0.00412, + "solanamainnet": 5, + "soneium": 0.00719, + "sonic": 6.61, + "sonicsvm": 0.0138, + "soon": 0.00102, + "stride": 6.88, + "superseed": 0.00396, + "superpositionmainnet": 0.00152, + "swell": 0.00454, + "taiko": 0.00726, + "tangle": 3.13, + "telos": 22.7, + "torus": 5.82, + "treasure": 789, + "trumpchain": 0.12, + "unichain": 0.00102, + "unitzero": 6.11, + "vana": 0.372, + "viction": 9.34, + "worldchain": 0.00348, + "xai": 19.6, + "xlayer": 0.373, + "xpla": 41.3, + "zeronetwork": 0.00267, + "zetachain": 7.57, + "zircuit": 0.00379, + "zklink": 0.00102, + "zksync": 0.00284, + "zoramainnet": 0.00832 +} diff --git a/typescript/infra/config/environments/mainnet3/balances/desiredRelayerBalances.json b/typescript/infra/config/environments/mainnet3/balances/desiredRelayerBalances.json new file mode 100644 index 0000000000..88f37a8cee --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/balances/desiredRelayerBalances.json @@ -0,0 +1,124 @@ +{ + "abstract": "0", + "alephzeroevmmainnet": "110", + "ancient8": "0.5", + "apechain": "50", + "appchain": "0.189", + "arbitrum": "0.5", + "arbitrumnova": "0.05", + "artela": "1530", + "arthera": "864", + "astar": "527", + "astarzkevm": "0.05", + "aurora": "0.05", + "avalanche": "5", + "b3": "0.05", + "base": "0.57", + "bitlayer": "0.0162", + "blast": "0.2", + "bob": "0.2", + "boba": "0.05", + "bsc": "5", + "bsquared": "0.029", + "celo": "48.3", + "cheesechain": "21400", + "chilizmainnet": "800", + "conflux": "172", + "conwai": "7110", + "coredao": "36.5", + "corn": "0.001", + "cyber": "0.05", + "degenchain": "3850", + "dogechain": "100", + "duckchain": "5.14", + "eclipsemainnet": "0", + "endurance": "20", + "ethereum": "4.45", + "everclear": "0.0501", + "evmos": "1790", + "fantom": "100", + "flame": "15.6", + "flare": "1060", + "flowmainnet": "41", + "form": "0.05", + "fraxtal": "0.2", + "fusemainnet": "1010", + "glue": "67.2", + "gnosis": "25", + "gravity": "976", + "guru": "2240", + "harmony": "1370", + "hemi": "0.05", + "immutablezkevmmainnet": "33.1", + "inevm": "3", + "injective": "0", + "ink": "0.0904", + "kaia": "250", + "kroma": "0.05", + "linea": "1", + "lisk": "0.0625", + "lukso": "20", + "lumia": "25.7", + "lumiaprism": "32.7", + "mantapacific": "0.2", + "mantle": "111", + "matchain": "0.05", + "merlin": "0.00254", + "metal": "0.0816", + "metis": "3", + "mint": "0.05", + "mode": "0.2", + "molten": "76.1", + "moonbeam": "193", + "morph": "2.26", + "nero": "25", + "neutron": "0", + "oortmainnet": "2000", + "optimism": "0.5", + "orderly": "0.05", + "osmosis": "0", + "polygon": "135", + "polygonzkevm": "0.5", + "polynomialfi": "0.05", + "prom": "23.4", + "proofofplay": "0.05", + "rarichain": "0.0553", + "real": "0.1", + "redstone": "0.2", + "rivalz": "0.05", + "rootstockmainnet": "0.0193", + "sanko": "2", + "scroll": "0.5", + "sei": "83.2", + "shibarium": "74.7", + "snaxchain": "0.05", + "solanamainnet": "0", + "soneium": "0.0575", + "sonic": "52.9", + "sonicsvm": "0", + "soon": "0", + "stride": "0", + "superpositionmainnet": "0.05", + "superseed": "0.05", + "swell": "0.05", + "taiko": "0.2", + "tangle": "25", + "telos": "182", + "torus": "46.6", + "treasure": "6310", + "trumpchain": "0.96", + "unichain": "0.05", + "unitzero": "50", + "vana": "2.98", + "viction": "74.7", + "worldchain": "0.2", + "xai": "157", + "xlayer": "2.98", + "xpla": "330", + "zeronetwork": "0.05", + "zetachain": "60.6", + "zircuit": "0.0303", + "zklink": "0.05", + "zksync": "0.05", + "zoramainnet": "0.2" +} diff --git a/typescript/infra/config/environments/mainnet3/balances/highUrgencyRelayerBalance.json b/typescript/infra/config/environments/mainnet3/balances/highUrgencyRelayerBalance.json new file mode 100644 index 0000000000..93f6db4f26 --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/balances/highUrgencyRelayerBalance.json @@ -0,0 +1,122 @@ +{ + "alephzeroevmmainnet": "27.6", + "ancient8": "0.00746", + "apechain": "7.12", + "appchain": "0.0472", + "arbitrum": "0.0804", + "arbitrumnova": "0.00204", + "artela": "382", + "arthera": "216", + "astar": "132", + "astarzkevm": "0.00738", + "aurora": "0.00204", + "avalanche": "0.89", + "b3": "0.00854", + "base": "0.142", + "bitlayer": "0.00404", + "blast": "0.0118", + "bob": "0.00704", + "boba": "0.00702", + "bsc": "0.47", + "bsquared": "0.00724", + "celo": "12.1", + "cheesechain": "5340", + "chilizmainnet": "200", + "conflux": "43", + "conwai": "1780", + "coredao": "9.12", + "corn": "0.0000632", + "cyber": "0.00886", + "degenchain": "962", + "dogechain": "21", + "duckchain": "1.29", + "eclipsemainnet": "0.05", + "endurance": "4.46", + "ethereum": "1.11", + "everclear": "0.0125", + "evmos": "448", + "fantom": "13.2", + "flame": "3.9", + "flare": "266", + "flowmainnet": "10.3", + "form": "0.0071", + "fraxtal": "0.032", + "fusemainnet": "252", + "glue": "16.8", + "gnosis": "6.24", + "gravity": "244", + "guru": "560", + "harmony": "342", + "hemi": "0.00204", + "immutablezkevmmainnet": "8.28", + "inevm": "0.342", + "injective": "0.342", + "ink": "0.0226", + "kaia": "33.6", + "kroma": "0.00682", + "linea": "0.22", + "lisk": "0.0156", + "lukso": "3.94", + "lumia": "6.42", + "lumiaprism": "8.18", + "mantapacific": "0.02", + "mantle": "27.8", + "matchain": "0.0096", + "merlin": "0.000636", + "metal": "0.0204", + "metis": "1", + "mint": "0.00924", + "mode": "0.027", + "molten": "19", + "moonbeam": "48.2", + "morph": "0.566", + "nero": "6.26", + "neutron": "23.6", + "oortmainnet": "400", + "optimism": "0.0708", + "orderly": "0.00942", + "polygon": "33.8", + "polygonzkevm": "0.0894", + "polynomialfi": "0.00872", + "prom": "5.84", + "proofofplay": "0.00204", + "rarichain": "0.0138", + "real": "0.00314", + "redstone": "0.00688", + "rivalz": "0.00204", + "rootstockmainnet": "0.00482", + "sanko": "0.316", + "scroll": "0.0338", + "sei": "20.8", + "shibarium": "18.7", + "snaxchain": "0.00824", + "solanamainnet": "10", + "soneium": "0.0144", + "sonic": "13.2", + "sonicsvm": "0.0276", + "soon": "0.00204", + "stride": "13.8", + "superpositionmainnet": "0.00304", + "superseed": "0.00792", + "swell": "0.00908", + "taiko": "0.019", + "tangle": "6.26", + "telos": "45.4", + "torus": "11.6", + "treasure": "1580", + "trumpchain": "0.24", + "unichain": "0.00204", + "unitzero": "12.2", + "vana": "0.744", + "viction": "18.7", + "worldchain": "0.00696", + "xai": "39.2", + "xlayer": "0.746", + "xpla": "82.6", + "zeronetwork": "0.00534", + "zetachain": "15.1", + "zircuit": "0.0094", + "zklink": "0.00204", + "zksync": "0.00568", + "zoramainnet": "0.0166" +} diff --git a/typescript/infra/config/environments/mainnet3/balances/lowUrgencyKeyFunderBalance.json b/typescript/infra/config/environments/mainnet3/balances/lowUrgencyKeyFunderBalance.json new file mode 100644 index 0000000000..292cff8a0e --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/balances/lowUrgencyKeyFunderBalance.json @@ -0,0 +1,122 @@ +{ + "alephzeroevmmainnet": "200", + "ancient8": "1", + "apechain": "100", + "appchain": "0.283", + "arbitrum": "1.2", + "arbitrumnova": "0.1", + "artela": "2290", + "arthera": "1300", + "astar": "791", + "astarzkevm": "0.1", + "aurora": "0.1", + "avalanche": "22", + "b3": "0.1", + "base": "1.1", + "bitlayer": "0.0242", + "blast": "0.4", + "bob": "0.4", + "boba": "0.1", + "bsc": "8", + "bsquared": "0.0434", + "celo": "308", + "cheesechain": "32000", + "chilizmainnet": "1200", + "conflux": "258", + "conwai": "10700", + "coredao": "54.7", + "corn": "0.002", + "cyber": "0.1", + "degenchain": "5770", + "dogechain": "200", + "duckchain": "10", + "eclipsemainnet": "0.3", + "endurance": "40", + "ethereum": "6.67", + "everclear": "0.1", + "evmos": "2690", + "fantom": "200", + "flame": "23.4", + "flare": "1600", + "flowmainnet": "61.6", + "form": "0.1", + "fraxtal": "0.4", + "fusemainnet": "1510", + "glue": "101", + "gnosis": "210", + "gravity": "1460", + "guru": "3360", + "harmony": "2050", + "hemi": "0.0122", + "immutablezkevmmainnet": "50", + "inevm": "6.1", + "injective": "2.05", + "ink": "0.136", + "kaia": "500", + "kroma": "0.1", + "linea": "2", + "lisk": "0.1", + "lukso": "40", + "lumia": "38.5", + "lumiaprism": "49.1", + "mantapacific": "0.4", + "mantle": "167", + "matchain": "0.0576", + "merlin": "0.00382", + "metal": "0.122", + "metis": "6", + "mint": "0.1", + "mode": "0.4", + "molten": "114", + "moonbeam": "700", + "morph": "3.4", + "nero": "37.6", + "neutron": "142", + "oortmainnet": "4000", + "optimism": "1.2", + "orderly": "0.1", + "polygon": "250", + "polygonzkevm": "1.1", + "polynomialfi": "0.1", + "prom": "36", + "proofofplay": "0.05", + "rarichain": "0.1", + "real": "0.2", + "redstone": "0.4", + "rivalz": "0.1", + "rootstockmainnet": "0.0289", + "sanko": "2", + "scroll": "1.1", + "sei": "125", + "shibarium": "112", + "snaxchain": "0.05", + "solanamainnet": "60", + "soneium": "0.1", + "sonic": "79.3", + "sonicsvm": "0.166", + "soon": "0.0122", + "stride": "82.6", + "superpositionmainnet": "0.1", + "superseed": "0.05", + "swell": "0.1", + "taiko": "0.4", + "tangle": "37.6", + "telos": "272", + "torus": "69.8", + "treasure": "9470", + "trumpchain": "1.44", + "unichain": "0.1", + "unitzero": "73.3", + "vana": "4.46", + "viction": "112", + "worldchain": "0.4", + "xai": "235", + "xlayer": "4.48", + "xpla": "496", + "zeronetwork": "0.1", + "zetachain": "90.8", + "zircuit": "0.0455", + "zklink": "0.05", + "zksync": "0.1", + "zoramainnet": "0.4" +} diff --git a/typescript/infra/package.json b/typescript/infra/package.json index a445ee7359..c27b8f12ba 100644 --- a/typescript/infra/package.json +++ b/typescript/infra/package.json @@ -28,6 +28,7 @@ "deep-object-diff": "^1.1.9", "dotenv": "^10.0.0", "json-stable-stringify": "^1.1.1", + "postgres": "^3.4.5", "prom-client": "^14.0.1", "prompts": "^2.4.2", "yaml": "2.4.5", diff --git a/typescript/infra/scripts/agent-utils.ts b/typescript/infra/scripts/agent-utils.ts index 260c51956b..5e0614591f 100644 --- a/typescript/infra/scripts/agent-utils.ts +++ b/typescript/infra/scripts/agent-utils.ts @@ -43,6 +43,8 @@ import { EnvironmentConfig, assertEnvironment, } from '../src/config/environment.js'; +import { BalanceThresholdType } from '../src/config/funding/balances.js'; +import { AlertType } from '../src/config/funding/grafanaAlerts.js'; import { Role } from '../src/roles.js'; import { assertContext, @@ -90,6 +92,12 @@ export function getArgs() { .alias('e', 'environment'); } +export function withBalanceThresholdConfig(args: Argv) { + return args + .describe('balanceThresholdConfig', 'balance threshold config') + .choices('balanceThresholdConfig', Object.values(BalanceThresholdType)); +} + export function withFork(args: Argv) { return args .describe('fork', 'network to fork') @@ -155,6 +163,13 @@ export function withChain(args: Argv) { .alias('c', 'chain'); } +export function withWrite(args: Argv) { + return args + .describe('write', 'Write output to file') + .boolean('write') + .default('write', false); +} + export function withChains(args: Argv, chainOptions?: ChainName[]) { return ( args @@ -200,6 +215,23 @@ export function withProtocol(args: Argv) { .demandOption('protocol'); } +export function withAlertType(args: Argv) { + return args + .describe('alertType', 'alert type') + .choices('alertType', Object.values(AlertType)); +} + +export function withAlertTypeRequired(args: Argv) { + return withAlertType(args).demandOption('alertType'); +} + +export function withConfirmAllChoices(args: Argv) { + return args + .describe('all', 'Confirm all choices') + .boolean('all') + .default('all', false); +} + export function withAgentRole(args: Argv) { return args .describe('role', 'agent role') diff --git a/typescript/infra/scripts/funding/calculate-relayer-daily-burn.ts b/typescript/infra/scripts/funding/calculate-relayer-daily-burn.ts new file mode 100644 index 0000000000..40c93e05ad --- /dev/null +++ b/typescript/infra/scripts/funding/calculate-relayer-daily-burn.ts @@ -0,0 +1,137 @@ +import postgres, { Sql } from 'postgres'; + +import { ChainMap } from '@hyperlane-xyz/sdk'; +import { rootLogger } from '@hyperlane-xyz/utils'; + +import rawDailyRelayerBurn from '../../config/environments/mainnet3/balances/dailyRelayerBurn.json'; +import { mainnet3SupportedChainNames } from '../../config/environments/mainnet3/supportedChainNames.js'; +import rawTokenPrices from '../../config/environments/mainnet3/tokenPrices.json'; +import { RELAYER_MIN_DOLLAR_BALANCE_PER_DAY } from '../../src/config/funding/balances.js'; +import { formatDailyRelayerBurn } from '../../src/funding/grafana.js'; +import { fetchLatestGCPSecret } from '../../src/utils/gcloud.js'; +import { writeJsonAtPath } from '../../src/utils/utils.js'; + +const tokenPrices: ChainMap = rawTokenPrices; +const currentDailyRelayerBurn: ChainMap = rawDailyRelayerBurn; + +const DAILY_BURN_PATH = + './config/environments/mainnet3/balances/dailyRelayerBurn.json'; + +const LOOK_BACK_DAYS = 10; // the number of days to look back for average destination tx costs +const MIN_NUMBER_OF_TXS = 200; // the minimum number of txs to consider for daily burn + +async function main() { + const chainsMissingInTokenPrices = mainnet3SupportedChainNames.filter( + (chain) => !(chain in tokenPrices), + ); + + if (chainsMissingInTokenPrices.length > 0) { + rootLogger.error( + `Token prices missing for chains: ${chainsMissingInTokenPrices.join( + ', ', + )} consider adding them to tokenPrices.json and running the script again.`, + ); + process.exit(1); + } + + const sql = await getReadOnlyScraperDb(); + let burnData: ChainMap; + try { + burnData = await getDailyRelayerBurn(sql); + } catch (err) { + rootLogger.error('Error fetching daily burn data:', err); + process.exit(1); + } finally { + await sql.end(); + } + + console.table(burnData); + + try { + rootLogger.info('Writing daily burn data to file..'); + writeJsonAtPath(DAILY_BURN_PATH, burnData); + rootLogger.info('Daily burn data written to file.'); + } catch (err) { + rootLogger.error('Error writing daily burn data to file:', err); + } +} + +async function getReadOnlyScraperDb() { + const credentialsUrl = await fetchLatestGCPSecret( + 'hyperlane-mainnet3-scraper3-db-read-only', + ); + return postgres(credentialsUrl); +} + +async function fetchDailyRelayerBurnData(sql: Sql) { + const results = await sql` + WITH + look_back_stats AS ( + SELECT + dest_domain.name AS domain_name, + COUNT(*) AS total_messages, + ( + SUM( + mv.destination_tx_gas_used * mv.destination_tx_effective_gas_price + ) / POWER(10, 18) + ) / COUNT(*) AS avg_tx_cost_native, + COUNT(*) / ${LOOK_BACK_DAYS} AS avg_daily_messages + FROM + message_view mv + LEFT JOIN DOMAIN dest_domain ON mv.destination_domain_id = dest_domain.id + WHERE + mv.send_occurred_at >= CURRENT_TIMESTAMP - (INTERVAL '1 day' * ${LOOK_BACK_DAYS}) + AND dest_domain.is_test_net IS FALSE + AND mv.destination_domain_id not in (1408864445, 1399811149) -- ignore sealevel chains as scraper does not capture all costs + AND mv.is_delivered IS TRUE + GROUP BY + dest_domain.name + ) + SELECT + domain_name as chain, + GREATEST( + avg_tx_cost_native * ${MIN_NUMBER_OF_TXS}, + avg_tx_cost_native * avg_daily_messages + ) as daily_burn + FROM + look_back_stats + ORDER BY + domain_name; + `; + return results; +} + +async function getDailyRelayerBurn(sql: Sql) { + const dailyRelayerBurnQueryResults = await fetchDailyRelayerBurnData(sql); + + const burn: Record = {}; + for (const chain of Object.keys(tokenPrices)) { + const row = dailyRelayerBurnQueryResults.find((row) => row.chain === chain); + + // minimum native balance required to maintain our desired minimum dollar balance in the relayer + const minNativeBalance = + RELAYER_MIN_DOLLAR_BALANCE_PER_DAY / parseFloat(tokenPrices[chain]); + + // some chains may have had no messages in the look back window so we set daily burn based on the minimum dollar balance + const proposedDailyRelayerBurn = + row === undefined + ? minNativeBalance + : Math.max(row.daily_burn, minNativeBalance); + + // only update the daily burn if the proposed daily burn is greater than the current daily burn + // add the chain to the daily burn if it doesn't exist + const newDailyRelayerBurn = + chain in currentDailyRelayerBurn + ? Math.max(proposedDailyRelayerBurn, currentDailyRelayerBurn[chain]) + : proposedDailyRelayerBurn; + + burn[chain] = formatDailyRelayerBurn(newDailyRelayerBurn); + } + + return burn; +} + +main().catch((err) => { + rootLogger.error('Error:', err); + process.exit(1); +}); diff --git a/typescript/infra/scripts/funding/read-alert-thresholds.ts b/typescript/infra/scripts/funding/read-alert-thresholds.ts new file mode 100644 index 0000000000..25d6ebe8fc --- /dev/null +++ b/typescript/infra/scripts/funding/read-alert-thresholds.ts @@ -0,0 +1,41 @@ +import yargs from 'yargs'; + +import { rootLogger } from '@hyperlane-xyz/utils'; + +import { THRESHOLD_CONFIG_PATH } from '../../src/config/funding/balances.js'; +import { alertConfigMapping } from '../../src/config/funding/grafanaAlerts.js'; +import { + getAlertThresholds, + sortThresholds, +} from '../../src/funding/grafana.js'; +import { writeJsonAtPath } from '../../src/utils/utils.js'; +import { withAlertTypeRequired, withWrite } from '../agent-utils.js'; + +async function main() { + const { alertType, write } = await withWrite( + withAlertTypeRequired(yargs(process.argv.slice(2))), + ).argv; + + const alertThresholds = await getAlertThresholds(alertType); + const sortedThresholds = sortThresholds(alertThresholds); + + console.table(sortedThresholds); + + if (write) { + rootLogger.info('Writing alert thresholds to file..'); + try { + writeJsonAtPath( + `${THRESHOLD_CONFIG_PATH}/${alertConfigMapping[alertType].configFileName}`, + sortedThresholds, + ); + rootLogger.info('Alert thresholds written to file.'); + } catch (e) { + rootLogger.error('Error writing alert thresholds to file:', e); + } + } +} + +main().catch((err) => { + rootLogger.error(err); + process.exit(1); +}); diff --git a/typescript/infra/scripts/funding/update-balance-threshold-config.ts b/typescript/infra/scripts/funding/update-balance-threshold-config.ts new file mode 100644 index 0000000000..cbc0e20239 --- /dev/null +++ b/typescript/infra/scripts/funding/update-balance-threshold-config.ts @@ -0,0 +1,111 @@ +import { checkbox } from '@inquirer/prompts'; +import yargs from 'yargs'; + +import { ChainMap } from '@hyperlane-xyz/sdk'; +import { rootLogger } from '@hyperlane-xyz/utils'; + +import rawDailyBurn from '../../config/environments/mainnet3/balances/dailyRelayerBurn.json'; +import { + BalanceThresholdType, + THRESHOLD_CONFIG_PATH, + balanceThresholdConfigMapping, +} from '../../src/config/funding/balances.js'; +import { + formatDailyRelayerBurn, + sortThresholds, +} from '../../src/funding/grafana.js'; +import { readJSONAtPath, writeJsonAtPath } from '../../src/utils/utils.js'; +import { + withBalanceThresholdConfig, + withConfirmAllChoices, +} from '../agent-utils.js'; + +const dailyBurn: ChainMap = rawDailyBurn; + +const exclusionList = ['osmosis']; + +async function main() { + const { balanceThresholdConfig, all } = await withConfirmAllChoices( + withBalanceThresholdConfig(yargs(process.argv.slice(2))), + ).argv; + + const configToUpdate: BalanceThresholdType[] = all + ? Object.values(BalanceThresholdType) + : balanceThresholdConfig + ? [balanceThresholdConfig] + : await checkbox({ + message: 'Select the balance threshold config to update', + choices: Object.values(BalanceThresholdType).map((config) => ({ + name: balanceThresholdConfigMapping[config].choiceLabel, + value: config, + checked: true, // default to all checked + })), + }); + + for (const config of configToUpdate) { + rootLogger.info(`Updating ${config} config`); + + let currentThresholds: ChainMap = {}; + const newThresholds: ChainMap = {}; + try { + currentThresholds = readJSONAtPath( + `${THRESHOLD_CONFIG_PATH}/${balanceThresholdConfigMapping[config].configFileName}`, + ); + } catch (e) { + rootLogger.error(`Error reading ${config} config: ${e}`); + } + + // Update the threshold for each chain, if it doesn't exist, create a new one + for (const chain in dailyBurn) { + if (!currentThresholds[chain]) { + // Skip chains in the exclusion list + if (exclusionList.includes(chain)) { + rootLogger.info(`Skipping ${chain} as it is in the exclusion list`); + continue; + } + + newThresholds[chain] = formatDailyRelayerBurn( + dailyBurn[chain] * + balanceThresholdConfigMapping[config].dailyRelayerBurnMultiplier, + ).toString(); + } else { + // This will ensure that chains where the desired threshold is 0 will be unchanged + if ( + config === BalanceThresholdType.RelayerBalance && + parseFloat(currentThresholds[chain]) === 0 + ) { + newThresholds[chain] = currentThresholds[chain]; + continue; + } + + newThresholds[chain] = Math.max( + formatDailyRelayerBurn( + dailyBurn[chain] * + balanceThresholdConfigMapping[config].dailyRelayerBurnMultiplier, + ), + parseFloat(currentThresholds[chain]), + ).toString(); + } + } + + const sortedThresholds = sortThresholds(newThresholds); + + try { + rootLogger.info(`Writing ${config} config to file..`); + writeJsonAtPath( + `${THRESHOLD_CONFIG_PATH}/${balanceThresholdConfigMapping[config].configFileName}`, + sortedThresholds, + ); + rootLogger.info(`Successfully updated ${config} config`); + } catch (e) { + rootLogger.error(`Error writing ${config} config: ${e}`); + } + } +} + +main() + .then() + .catch((e) => { + rootLogger.error(e); + process.exit(1); + }); diff --git a/typescript/infra/scripts/funding/write-alert.ts b/typescript/infra/scripts/funding/write-alert.ts new file mode 100644 index 0000000000..5245fd63fa --- /dev/null +++ b/typescript/infra/scripts/funding/write-alert.ts @@ -0,0 +1,72 @@ +import { checkbox } from '@inquirer/prompts'; +import yargs from 'yargs'; + +import { ChainMap } from '@hyperlane-xyz/sdk'; +import { rootLogger } from '@hyperlane-xyz/utils'; + +import { THRESHOLD_CONFIG_PATH } from '../../src/config/funding/balances.js'; +import { + AlertType, + alertConfigMapping, +} from '../../src/config/funding/grafanaAlerts.js'; +import { + fetchGrafanaAlert, + fetchServiceAccountToken, + generateQuery, + updateGrafanaAlert, +} from '../../src/funding/grafana.js'; +import { readJSONAtPath } from '../../src/utils/utils.js'; +import { withAlertType, withConfirmAllChoices } from '../agent-utils.js'; + +async function main() { + const { alertType, all } = await withConfirmAllChoices( + withAlertType(yargs(process.argv.slice(2))), + ).argv; + + const saToken = await fetchServiceAccountToken(); + + const alertsToUpdate: AlertType[] = all + ? Object.values(AlertType) + : alertType + ? [alertType] + : await checkbox({ + message: 'Select the alert type to update', + choices: Object.values(AlertType).map((alert) => ({ + name: alertConfigMapping[alert].choiceLabel, + value: alert, + checked: true, // default to all checked + })), + }); + + for (const alert of alertsToUpdate) { + // fetch alertRule config from Grafana + const alertRule = await fetchGrafanaAlert(alert, saToken); + + let thresholds: ChainMap = {}; + try { + thresholds = readJSONAtPath( + `${THRESHOLD_CONFIG_PATH}/${alertConfigMapping[alert].configFileName}`, + ); + } catch (e) { + rootLogger.error(`Error reading ${alert} config: ${e}`); + process.exit(1); + } + + const query = generateQuery(alert, thresholds); + + // only change the query + await updateGrafanaAlert( + alertConfigMapping[alert].grafanaAlertId, + alertRule.rawData, + query, + saToken, + ); + + rootLogger.info(`Updated ${alert} alert`); + } +} + +main().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/typescript/infra/src/agents/key-utils.ts b/typescript/infra/src/agents/key-utils.ts index 46262ed8ba..69392470b3 100644 --- a/typescript/infra/src/agents/key-utils.ts +++ b/typescript/infra/src/agents/key-utils.ts @@ -1,5 +1,4 @@ -import { dirname, join } from 'path'; -import { fileURLToPath } from 'url'; +import { join } from 'path'; import { ChainMap, ChainName } from '@hyperlane-xyz/sdk'; import { Address, objMap, rootLogger } from '@hyperlane-xyz/utils'; diff --git a/typescript/infra/src/config/funding/alert-query-templates.ts b/typescript/infra/src/config/funding/alert-query-templates.ts new file mode 100644 index 0000000000..1ddeabd1c4 --- /dev/null +++ b/typescript/infra/src/config/funding/alert-query-templates.ts @@ -0,0 +1,56 @@ +// alert queries currently need to support special cases i.e cross VM (sealevel and cosmos chains) and ata payer (sealevel). These special cases are hard coded for now. We aim to add cross VM support to the key and will be able to remove special casing in the future +export const LOW_URGENCY_KEY_FUNDER_HEADER = `# Note: use last_over_time(hyperlane_wallet_balance{}[1d]) to be resilient to gaps in the 'hyperlane_wallet_balance' +# that occur due to key funder only running every hour or so. + +min by (chain, wallet_address, wallet_name) ( + # Mainnets`; + +export const LOW_URGENCY_KEY_FUNDER_FOOTER = ` # Mainnets that don't use key-funder and all funds are stored in the relayer's wallet + # Eclipse + last_over_time(hyperlane_wallet_balance{wallet_name="relayer", hyperlane_context="hyperlane", chain=~"eclipsemainnet"}[1d]) - 1 or + # Any ATA payer on Eclipse + last_over_time(hyperlane_wallet_balance{wallet_name=~".*/ata-payer", chain=~"eclipsemainnet"}[1d]) - 0.01 or + # SOL/eclipsemainnet-solanamainnet + last_over_time(hyperlane_wallet_balance{wallet_name=~"SOL/eclipsemainnet-solanamainnet/ata-payer | USDC/eclipsemainnet-ethereum-solanamainnet/ata-payer", chain=~"eclipsemainnet"}[1d]) - 0.1 or + + # Solana + last_over_time(hyperlane_wallet_balance{wallet_name="relayer", hyperlane_context="hyperlane", chain=~"solanamainnet"}[1d]) - 27 or + # Any ATA payer on Solana + last_over_time(hyperlane_wallet_balance{wallet_name=~".*/ata-payer", chain=~"solanamainnet"}[1d]) - 0.2 or + # Any ATA payer on Solana + last_over_time(hyperlane_wallet_balance{wallet_name=~"USDC/eclipsemainnet-ethereum-solanamainnet/ata-payer", chain=~"solanamainnet"}[1d]) - 0.8 or + + # Neutron context + last_over_time(hyperlane_wallet_balance{wallet_name="relayer", chain="mantapacific", hyperlane_context="neutron"}[1d]) - 0.3 or + last_over_time(hyperlane_wallet_balance{wallet_name="relayer", chain="arbitrum", hyperlane_context="neutron"}[1d]) - 0.3 or + last_over_time(hyperlane_wallet_balance{wallet_name="relayer", chain="neutron", hyperlane_context="neutron"}[1d]) - 1500 or + + # Injective + (last_over_time(hyperlane_wallet_balance{wallet_name="relayer", chain="injective", wallet_address!~"inj1ddw6pm84zmtpms0gpknfsejkk9v6t0ergjpl30|inj1ds32d5t26j7gauvtly86lk6uh06ear3jvqllaw"}[1d]) / 1000000000000) - 3 or + + # Stride + (last_over_time(hyperlane_wallet_balance{wallet_name="relayer", chain="stride"}[1d])) - 10 +`; + +// TODO: add footer for LOW_URGENCY_ENG_KEY_FUNDER + +export const HIGH_URGENCY_RELAYER_HEADER = `min by (chain, wallet_address, wallet_name) ( + # Mainnets`; + +export const HIGH_URGENCY_RELAYER_FOOTER = ` # Special contexts already have hyperlane_context set correctly + + # Eclipse + last_over_time(hyperlane_wallet_balance{wallet_name=~".*/ata-payer", chain=~"eclipsemainnet"}[1d]) - 0.001 or + + # Solana + last_over_time(hyperlane_wallet_balance{wallet_name=~".*/ata-payer", chain=~"solanamainnet"}[1d]) - 0.1 or + + # SOON + # Any ATA payer on SOON + last_over_time(hyperlane_wallet_balance{wallet_name=~".*/ata-payer", chain=~"soon"}[1d]) - 0.005 or + + # Neutron context lines stay with neutron context + last_over_time(hyperlane_wallet_balance{wallet_name="relayer", chain="mantapacific", hyperlane_context="neutron"}[1d]) - 0.02 or + last_over_time(hyperlane_wallet_balance{wallet_name="relayer", chain="arbitrum", hyperlane_context="neutron"}[1d]) - 0.02 or + last_over_time(hyperlane_wallet_balance{wallet_name="relayer", chain="neutron", hyperlane_context="neutron"}[1d]) - 0.7 +`; diff --git a/typescript/infra/src/config/funding/balances.ts b/typescript/infra/src/config/funding/balances.ts new file mode 100644 index 0000000000..ee0dcda0e7 --- /dev/null +++ b/typescript/infra/src/config/funding/balances.ts @@ -0,0 +1,45 @@ +export enum BalanceThresholdType { + RelayerBalance = 'relayerBalance', + LowUrgencyKeyFunderBalance = 'lowUrgencyKeyFunderBalance', + LowUrgencyEngKeyFunderBalance = 'lowUrgencyEngKeyFunderBalance', + HighUrgencyRelayerBalance = 'highUrgencyRelayerBalance', +} + +interface BalanceThresholdConfig { + configFileName: string; + dailyRelayerBurnMultiplier: number; + choiceLabel: string; +} + +export const THRESHOLD_CONFIG_PATH = './config/environments/mainnet3/balances'; + +const RELAYER_BALANCE_TARGET_DAYS = 8; +const RELAYER_MIN_DOLLAR_BALANCE_TARGET = 25; +export const RELAYER_MIN_DOLLAR_BALANCE_PER_DAY = + RELAYER_MIN_DOLLAR_BALANCE_TARGET / RELAYER_BALANCE_TARGET_DAYS; + +export const balanceThresholdConfigMapping: Record< + BalanceThresholdType, + BalanceThresholdConfig +> = { + [BalanceThresholdType.RelayerBalance]: { + configFileName: 'desiredRelayerBalances.json', + dailyRelayerBurnMultiplier: RELAYER_BALANCE_TARGET_DAYS, + choiceLabel: 'Desired Relayer Balance', + }, + [BalanceThresholdType.LowUrgencyKeyFunderBalance]: { + configFileName: `${[BalanceThresholdType.LowUrgencyKeyFunderBalance]}.json`, + dailyRelayerBurnMultiplier: 12, + choiceLabel: 'Low Urgency Key Funder Balance', + }, + [BalanceThresholdType.LowUrgencyEngKeyFunderBalance]: { + configFileName: `${BalanceThresholdType.LowUrgencyEngKeyFunderBalance}.json`, + dailyRelayerBurnMultiplier: 6, + choiceLabel: 'Low Urgency Eng Key Funder Balance', + }, + [BalanceThresholdType.HighUrgencyRelayerBalance]: { + configFileName: `${BalanceThresholdType.HighUrgencyRelayerBalance}.json`, + dailyRelayerBurnMultiplier: 2, + choiceLabel: 'High Urgency Relayer Balance', + }, +}; diff --git a/typescript/infra/src/config/funding/grafanaAlerts.ts b/typescript/infra/src/config/funding/grafanaAlerts.ts new file mode 100644 index 0000000000..aee95273e6 --- /dev/null +++ b/typescript/infra/src/config/funding/grafanaAlerts.ts @@ -0,0 +1,166 @@ +import { + HIGH_URGENCY_RELAYER_FOOTER, + HIGH_URGENCY_RELAYER_HEADER, + LOW_URGENCY_KEY_FUNDER_FOOTER, + LOW_URGENCY_KEY_FUNDER_HEADER, +} from './alert-query-templates.js'; +import { + BalanceThresholdType, + balanceThresholdConfigMapping, +} from './balances.js'; + +export const GRAFANA_URL = 'https://abacusworks.grafana.net'; + +export enum AlertType { + LowUrgencyKeyFunderBalance = 'lowUrgencyKeyFunderBalance', + LowUrgencyEngKeyFunderBalance = 'lowUrgencyEngKeyFunderBalance', + HighUrgencyRelayerBalance = 'highUrgencyRelayerBalance', +} + +interface AlertConfig { + walletName: WalletName; + grafanaAlertId: string; + configFileName: string; + choiceLabel: string; + queryTemplate: { + header: string; + footer: string; + }; +} + +export enum WalletName { + KeyFunder = 'keyFunder', + Relayer = 'relayer', + // ATAPayer = 'ataPayer', +} + +export const walletNameQueryFormat: Record = { + [WalletName.KeyFunder]: 'key-funder', + [WalletName.Relayer]: 'relayer', + // [WalletName.ATAPayer]: '.*ata-payer +}; + +export const alertConfigMapping: Record = { + [AlertType.LowUrgencyKeyFunderBalance]: { + walletName: WalletName.KeyFunder, + grafanaAlertId: 'ae9z3blz6fj0gb', + configFileName: + balanceThresholdConfigMapping[ + BalanceThresholdType.LowUrgencyKeyFunderBalance + ].configFileName, + choiceLabel: + balanceThresholdConfigMapping[ + BalanceThresholdType.LowUrgencyKeyFunderBalance + ].choiceLabel, + queryTemplate: { + header: LOW_URGENCY_KEY_FUNDER_HEADER, + footer: LOW_URGENCY_KEY_FUNDER_FOOTER, + }, + }, + [AlertType.LowUrgencyEngKeyFunderBalance]: { + walletName: WalletName.KeyFunder, + grafanaAlertId: 'ceb9c63qs7fuoe', + configFileName: + balanceThresholdConfigMapping[ + BalanceThresholdType.LowUrgencyEngKeyFunderBalance + ].configFileName, + choiceLabel: + balanceThresholdConfigMapping[ + BalanceThresholdType.LowUrgencyEngKeyFunderBalance + ].choiceLabel, + queryTemplate: { + header: LOW_URGENCY_KEY_FUNDER_HEADER, + footer: LOW_URGENCY_KEY_FUNDER_FOOTER, + }, + }, + [AlertType.HighUrgencyRelayerBalance]: { + walletName: WalletName.Relayer, + grafanaAlertId: 'beb9c2jwhacqoe', + configFileName: + balanceThresholdConfigMapping[ + BalanceThresholdType.HighUrgencyRelayerBalance + ].configFileName, + choiceLabel: + balanceThresholdConfigMapping[ + BalanceThresholdType.HighUrgencyRelayerBalance + ].choiceLabel, + queryTemplate: { + header: HIGH_URGENCY_RELAYER_HEADER, + footer: HIGH_URGENCY_RELAYER_FOOTER, + }, + }, +}; + +interface NotificationSettings { + receiver: string; + group_by: string[]; +} + +interface AlertQueryModel { + editorMode?: string; + exemplar?: boolean; + expr: string; + instant?: boolean; + intervalMs: number; + legendFormat?: string; + maxDataPoints: number; + range?: boolean; + refId: string; + conditions?: Array<{ + evaluator: { + params: number[]; + type: string; + }; + operator: { + type: string; + }; + query: { + params: any[]; + }; + reducer: { + params: any[]; + type: string; + }; + type: string; + }>; + datasource?: { + name?: string; + type: string; + uid: string; + }; + expression?: string; + type?: string; +} + +interface AlertQuery { + refId: string; + queryType: string; + relativeTimeRange: { + from: number; + to: number; + }; + datasourceUid: string; + model: AlertQueryModel; +} + +// interface defined based on documentation at https://grafana.com/docs/grafana/latest/developers/http_api/alerting_provisioning/#span-idprovisioned-alert-rulespan-provisionedalertrule +export interface ProvisionedAlertRule { + id: number; + uid: string; + orgID: number; + folderUID: string; + ruleGroup: string; + title: string; + condition: string; + data: AlertQuery[]; + noDataState: string; + execErrState: string; + + updated: string; + for: string; + + annotations?: Record; + labels?: Record; + isPaused?: boolean; + notification_settings?: NotificationSettings; +} diff --git a/typescript/infra/src/funding/grafana.ts b/typescript/infra/src/funding/grafana.ts new file mode 100644 index 0000000000..2f1763396f --- /dev/null +++ b/typescript/infra/src/funding/grafana.ts @@ -0,0 +1,207 @@ +import { ChainMap } from '@hyperlane-xyz/sdk'; +import { rootLogger } from '@hyperlane-xyz/utils'; + +import { + AlertType, + GRAFANA_URL, + ProvisionedAlertRule, + WalletName, + alertConfigMapping, + walletNameQueryFormat, +} from '../config/funding/grafanaAlerts.js'; +import { fetchGCPSecret } from '../utils/gcloud.js'; + +export const logger = rootLogger.child({ module: 'grafana' }); + +export function formatDailyRelayerBurn(dailyRelayerBurn: number): number { + return Number(dailyRelayerBurn.toPrecision(3)); +} + +export async function fetchGrafanaAlert(alertType: AlertType, saToken: string) { + const response = await fetch( + `${GRAFANA_URL}/api/v1/provisioning/alert-rules/${alertConfigMapping[alertType].grafanaAlertId}`, + { + headers: { + Authorization: `Bearer ${saToken}`, + 'Content-Type': 'application/json', + }, + }, + ); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = (await response.json()) as ProvisionedAlertRule; + + const queries = data.data.map((d) => d.model.expr); + + return { + title: data.title, + queries, + rawData: data, + }; +} + +export async function exportGrafanaAlert( + alertType: AlertType, + saToken: string, + format: string = 'json', +) { + const response = await fetch( + `${GRAFANA_URL}/api/v1/provisioning/alert-rules/${alertConfigMapping[alertType].grafanaAlertId}/export?format=${format}`, + { + headers: { + Authorization: `Bearer ${saToken}`, + 'Content-Type': 'application/json', + }, + }, + ); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + return response; +} + +function parsePromQLQuery( + query: string, + walletName: WalletName, +): ChainMap { + const balances: ChainMap = {}; + const alertRegex = getAlertRegex(walletName); + + // Get all matches + const matches = Array.from(query.matchAll(alertRegex)); + for (const match of matches) { + const [_, chain, balanceStr] = match; + const minBalance = balanceStr; + + balances[chain] = minBalance; + } + + return Object.fromEntries(Object.entries(balances).sort()); +} + +function getAlertRegex(walletName: WalletName): RegExp { + switch (walletName) { + case WalletName.KeyFunder: + return /wallet_name="key-funder", chain="([^"]+)"[^-]+ - ([0-9.]+)/g; + case WalletName.Relayer: + return /wallet_name="relayer", chain="([^"]+)"[^-]+ - ([0-9.]+)/g; + default: + throw new Error(`Unknown wallet name: ${walletName}`); + } +} + +export async function getAlertThresholds( + alertType: AlertType, +): Promise> { + const saToken = await fetchServiceAccountToken(); + const alert = await fetchGrafanaAlert(alertType, saToken); + const alertQuery = alert.queries[0]; + const walletName = alertConfigMapping[alertType].walletName; + return parsePromQLQuery(alertQuery, walletName); +} + +export async function fetchServiceAccountToken(): Promise { + let saToken: string | undefined; + + try { + saToken = (await fetchGCPSecret( + 'grafana-balance-alert-thresholds-token', + false, + )) as string; + } catch (error) { + logger.error( + 'Error fetching grafana service account token from GCP secrets:', + error, + ); + throw error; + } + + return saToken; +} + +export async function updateGrafanaAlert( + alertUid: string, + existingAlert: ProvisionedAlertRule, + newQuery: string, + saToken: string, +) { + // Create the updated rule based on the existing one + const updatedRule: ProvisionedAlertRule = { + ...existingAlert, + data: existingAlert.data.map((d) => ({ + ...d, + model: { + ...d.model, + expr: newQuery, + }, + })), + }; + + const response = await fetch( + `${GRAFANA_URL}/api/v1/provisioning/alert-rules/${alertUid}`, + { + method: 'PUT', + headers: { + Authorization: `Bearer ${saToken}`, + 'Content-Type': 'application/json', + 'X-Disable-Provenance': 'true', + }, + body: JSON.stringify(updatedRule), + }, + ); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error( + `Failed to update alert: ${response.status} ${JSON.stringify(errorData)}`, + ); + } + + return response.json(); +} + +export function generateQuery( + alertType: AlertType, + thresholds: ChainMap, +): string { + const config = alertConfigMapping[alertType]; + const walletQueryName = walletNameQueryFormat[config.walletName]; + + // TODO: abstract away special handling for relayer queries that need hyperlane_context + const needsHyperlaneContext = config.walletName === WalletName.Relayer; + + const queryFragments = Object.entries(thresholds).map( + ([chain, minBalance]) => { + const labels = [`wallet_name="${walletQueryName}"`, `chain="${chain}"`]; + if (needsHyperlaneContext) { + labels.push('hyperlane_context="hyperlane"'); + } + return `last_over_time(hyperlane_wallet_balance{${labels.join( + ', ', + )}}[1d]) - ${minBalance} or`; + }, + ); + + return `${config.queryTemplate.header} + ${queryFragments.join('\n ')} + +${config.queryTemplate.footer} +)`; +} + +export function sortThresholds( + newThresholds: ChainMap, +): ChainMap { + const orderedThresholds: ChainMap = {}; + Object.keys(newThresholds) + .sort() + .forEach((key) => { + orderedThresholds[key] = newThresholds[key]; + }); + return orderedThresholds; +} diff --git a/yarn.lock b/yarn.lock index 5728616979..60dd5e16b7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7503,6 +7503,7 @@ __metadata: hardhat: "npm:^2.22.2" json-stable-stringify: "npm:^1.1.1" mocha: "npm:^10.2.0" + postgres: "npm:^3.4.5" prettier: "npm:^2.8.8" prom-client: "npm:^14.0.1" prompts: "npm:^2.4.2" @@ -30747,6 +30748,13 @@ __metadata: languageName: node linkType: hard +"postgres@npm:^3.4.5": + version: 3.4.5 + resolution: "postgres@npm:3.4.5" + checksum: 10/b51341a8640bd63c42caf2a866c8b6bcac0d4cc1b85985f01eb3fd941eb7bf3fe5200bcf6983eaa1d42ae7555d85f4ff51d35916b5543b95aecfe98b76213611 + languageName: node + linkType: hard + "preact@npm:^10.16.0, preact@npm:^10.24.2": version: 10.24.3 resolution: "preact@npm:10.24.3" From d2ba3dea6e92df8d686596b9880707384d3ccf07 Mon Sep 17 00:00:00 2001 From: Jason Guo <33064781+Xaroz@users.noreply.github.com> Date: Thu, 30 Jan 2025 14:41:36 -0400 Subject: [PATCH 06/11] feat(infra): Add script to check warp route pod status (#5335) ### Description - Add script to check warp route monitor status by getting the pod using a given `warpRouteId` - The script will also return logs equal to the amount determined by the `LOG_AMOUNT` constant - Logs a link to the grafana dashboard with the given `warpRouteId` - Updated `withWarpRouteId` function to enforce choices for `WarpRouteIds` ### Drive-by changes No ### Related issues fixes #5247 ### Backward compatibility Yes ### Testing Manual ![image](https://github.com/user-attachments/assets/5de7c2ff-29d7-40c0-a442-c895577076e7) --- typescript/infra/scripts/agent-utils.ts | 5 +- .../scripts/warp-routes/monitor/status.ts | 106 ++++++++++++++++++ typescript/infra/src/utils/helm.ts | 17 +++ 3 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 typescript/infra/scripts/warp-routes/monitor/status.ts diff --git a/typescript/infra/scripts/agent-utils.ts b/typescript/infra/scripts/agent-utils.ts index 5e0614591f..07b9abfb24 100644 --- a/typescript/infra/scripts/agent-utils.ts +++ b/typescript/infra/scripts/agent-utils.ts @@ -200,7 +200,10 @@ export function withOutputFile(args: Argv) { } export function withWarpRouteId(args: Argv) { - return args.describe('warpRouteId', 'warp route id').string('warpRouteId'); + return args + .describe('warpRouteId', 'warp route id') + .string('warpRouteId') + .choices('warpRouteId', Object.values(WarpRouteIds)); } export function withWarpRouteIdRequired(args: Argv) { diff --git a/typescript/infra/scripts/warp-routes/monitor/status.ts b/typescript/infra/scripts/warp-routes/monitor/status.ts new file mode 100644 index 0000000000..a6908a1fe0 --- /dev/null +++ b/typescript/infra/scripts/warp-routes/monitor/status.ts @@ -0,0 +1,106 @@ +import chalk from 'chalk'; + +import { + LogFormat, + LogLevel, + configureRootLogger, + rootLogger, +} from '@hyperlane-xyz/utils'; + +import { HelmManager } from '../../../src/utils/helm.js'; +import { WarpRouteMonitorHelmManager } from '../../../src/warp/helm.js'; +import { + assertCorrectKubeContext, + getArgs, + withWarpRouteIdRequired, +} from '../../agent-utils.js'; +import { getEnvironmentConfig } from '../../core-utils.js'; + +const orange = chalk.hex('#FFA500'); +const GRAFANA_LINK = + 'https://abacusworks.grafana.net/d/ddz6ma94rnzswc/warp-routes?orgId=1&var-warp_route_id='; +const LOG_AMOUNT = 5; + +async function main() { + configureRootLogger(LogFormat.Pretty, LogLevel.Info); + const { environment, warpRouteId } = await withWarpRouteIdRequired( + getArgs(), + ).parse(); + + const config = getEnvironmentConfig(environment); + await assertCorrectKubeContext(config); + + try { + const podWarpRouteId = `${WarpRouteMonitorHelmManager.getHelmReleaseName( + warpRouteId, + )}-0`; + + rootLogger.info(chalk.grey.italic(`Fetching pod status...`)); + const pod = HelmManager.runK8sCommand( + 'get pod', + podWarpRouteId, + environment, + ); + rootLogger.info(chalk.green(pod)); + + rootLogger.info(chalk.gray.italic(`Fetching latest logs...`)); + const latestLogs = HelmManager.runK8sCommand( + 'logs', + podWarpRouteId, + environment, + [`--tail=${LOG_AMOUNT}`], + ); + formatAndPrintLogs(latestLogs); + + rootLogger.info( + orange.bold(`Grafana Dashboard Link: ${GRAFANA_LINK}${warpRouteId}`), + ); + } catch (error) { + rootLogger.error(error); + process.exit(1); + } +} + +function formatAndPrintLogs(rawLogs: string) { + try { + const logs = rawLogs + .trim() + .split('\n') + .map((line) => JSON.parse(line)); + logs.forEach((log) => { + const { time, module, msg, labels, balance, valueUSD } = log; + const timestamp = new Date(time).toISOString(); + const chain = labels?.chain_name || 'Unknown Chain'; + const token = labels?.token_name || 'Unknown Token'; + const warpRoute = labels?.warp_route_id || 'Unknown Warp Route'; + const tokenStandard = labels?.token_standard || 'Unknown Standard'; + const tokenAddress = labels?.token_address || 'Unknown Token Address'; + const walletAddress = labels?.wallet_address || 'Unknown Wallet'; + + let logMessage = + chalk.gray(`[${timestamp}] `) + chalk.white(`[${module}] `); + logMessage += chalk.blue(`${warpRoute} `); + logMessage += chalk.green(`${chain} `); + logMessage += chalk.blue.italic(`Token: ${token} (${tokenAddress}) `); + logMessage += chalk.green.italic(`${tokenStandard} `); + logMessage += chalk.blue.italic(`Wallet: ${walletAddress} `); + + if (balance) { + logMessage += chalk.yellow.italic(`Balance: ${balance} `); + } + if (valueUSD) { + logMessage += chalk.green.italic(`Value (USD): ${valueUSD} `); + } + logMessage += chalk.white(`→ ${msg}\n`); + + rootLogger.info(logMessage); + }); + } catch (error) { + rootLogger.error(`Failed to parse logs: ${error}`); + } +} + +main().catch((err) => { + rootLogger.error('Error in main:', err); + process.exit(1); +}); diff --git a/typescript/infra/src/utils/helm.ts b/typescript/infra/src/utils/helm.ts index 4bd23f1047..4d18a1fc43 100644 --- a/typescript/infra/src/utils/helm.ts +++ b/typescript/infra/src/utils/helm.ts @@ -1,3 +1,5 @@ +import { execSync } from 'child_process'; + import { DockerConfig } from '../config/agent/agent.js'; import { HelmChartConfig, @@ -211,4 +213,19 @@ export abstract class HelmManager { // Split on new lines and remove empty strings return output.split('\n').filter(Boolean); } + + static runK8sCommand( + command: string, + podId: string, + namespace: string, + args: string[] = [], + ) { + const argsString = args.join(' '); + return execSync( + `kubectl ${command} ${podId} -n ${namespace} ${argsString}`, + { + encoding: 'utf-8', + }, + ); + } } From 30eeecf3f9f0ff7e765a5415ce0b1a559e482130 Mon Sep 17 00:00:00 2001 From: Lee <6251863+ltyu@users.noreply.github.com> Date: Thu, 30 Jan 2025 13:58:11 -0500 Subject: [PATCH 07/11] chore: optimize warp config tests (#5341) ### Description This PR optimizes the warp config tests by moving the registry fetching into `before()` and reading it in memory. ### Backward compatibility Yes ### Testing Unit Tests --- typescript/infra/test/warp-configs.test.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/typescript/infra/test/warp-configs.test.ts b/typescript/infra/test/warp-configs.test.ts index e875ec0d3c..8144e23810 100644 --- a/typescript/infra/test/warp-configs.test.ts +++ b/typescript/infra/test/warp-configs.test.ts @@ -16,27 +16,25 @@ describe('Warp Configs', async function () { const ENV = 'mainnet3'; const warpIdsToCheck = Object.keys(warpConfigGetterMap); let multiProvider: MultiProvider; + let configsFromGithub; - before(async () => { + before(async function () { multiProvider = (await getHyperlaneCore(ENV)).multiProvider; + configsFromGithub = await getGithubRegistry().getWarpDeployConfigs(); }); const envConfig = getEnvironmentConfig(ENV); for (const warpRouteId of warpIdsToCheck) { - it(`should match Github Registry configs for ${warpRouteId}`, async () => { + it(`should match Github Registry configs for ${warpRouteId}`, async function () { const warpConfig = await getWarpConfig( multiProvider, envConfig, warpRouteId, ); - const githubRegistry = getGithubRegistry(); - const configsFromGithub = await githubRegistry.getWarpDeployConfig( - warpRouteId, - ); const { mergedObject, isInvalid } = diffObjMerge( warpConfig, - configsFromGithub!, + configsFromGithub![warpRouteId], ); if (isInvalid) { From bed81f7bc16ae603eaba48c0ba558375a01b85c5 Mon Sep 17 00:00:00 2001 From: Trevor Porter Date: Fri, 31 Jan 2025 12:36:47 +0000 Subject: [PATCH 08/11] feat: lower some IGP quotes (#5347) ### Description - Base and OP cut in half - Lower the ethereum gas price ### Drive-by changes ### Related issues ### Backward compatibility ### Testing --- rust/sealevel/environments/mainnet3/gas-oracle-configs.json | 4 ++-- typescript/infra/config/environments/mainnet3/gasPrices.json | 2 +- typescript/infra/src/config/gas-oracle.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/rust/sealevel/environments/mainnet3/gas-oracle-configs.json b/rust/sealevel/environments/mainnet3/gas-oracle-configs.json index 510674b24e..5e80c9db80 100644 --- a/rust/sealevel/environments/mainnet3/gas-oracle-configs.json +++ b/rust/sealevel/environments/mainnet3/gas-oracle-configs.json @@ -19,7 +19,7 @@ "base": { "oracleConfig": { "tokenExchangeRate": "203047922474445771936", - "gasPrice": "502398873", + "gasPrice": "251199323", "tokenDecimals": 18 }, "overhead": 166887 @@ -67,7 +67,7 @@ "optimism": { "oracleConfig": { "tokenExchangeRate": "203047922474445771936", - "gasPrice": "502398873", + "gasPrice": "251199323", "tokenDecimals": 18 }, "overhead": 166887 diff --git a/typescript/infra/config/environments/mainnet3/gasPrices.json b/typescript/infra/config/environments/mainnet3/gasPrices.json index fc8fb2ba2b..027dff1dae 100644 --- a/typescript/infra/config/environments/mainnet3/gasPrices.json +++ b/typescript/infra/config/environments/mainnet3/gasPrices.json @@ -140,7 +140,7 @@ "decimals": 9 }, "ethereum": { - "amount": "20.047740244", + "amount": "10", "decimals": 9 }, "everclear": { diff --git a/typescript/infra/src/config/gas-oracle.ts b/typescript/infra/src/config/gas-oracle.ts index 97a627e127..777add6f9c 100644 --- a/typescript/infra/src/config/gas-oracle.ts +++ b/typescript/infra/src/config/gas-oracle.ts @@ -171,7 +171,7 @@ function getMinUsdCost(local: ChainName, remote: ChainName): number { // aren't accounted for directly in the gas price. arbitrum: 0.5, ancient8: 0.5, - base: 0.5, + base: 0.25, blast: 0.5, bob: 0.5, fraxtal: 0.5, @@ -179,7 +179,7 @@ function getMinUsdCost(local: ChainName, remote: ChainName): number { mantapacific: 0.5, mantle: 0.5, mode: 0.5, - optimism: 0.5, + optimism: 0.25, polygonzkevm: 0.5, // Scroll is more expensive than the rest due to higher L1 fees scroll: 1.5, From ba50e62fc8e9a0c71f1a99add26acb1020dd0873 Mon Sep 17 00:00:00 2001 From: mshojaei-txfusion <138107084+mshojaei-txfusion@users.noreply.github.com> Date: Fri, 31 Jan 2025 17:29:17 +0330 Subject: [PATCH 09/11] fix: browser compatibility checks (#5288) ### Description This PR enhances the ESLint configuration to restrict the usage of Node.js built-in modules across the codebase. This is particularly important for the SDK package which needs to maintain browser compatibility for frontend integrations. Key changes: - Added comprehensive ESLint rules to prevent usage of Node.js specific modules (fs, path, crypto, etc.) - Updated error messages to guide developers towards environment-agnostic alternatives. (alternatives can be mentioned later on) - Created a separate ESLint config for the Solidity package - Excluded test files and AWS-related code from these restrictions ### Drive-by changes - Added ESLint dependency to solidity/package.json - Removed redundant eslint-disable comments from test files - Updated lint script in solidity/package.json to use the new config ### Related issues https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/5277 ### Backward compatibility Yes. These changes only affect development-time linting and do not impact runtime behavior. ### Testing Manual testing: - Verified ESLint rules correctly flag Node.js built-in module imports - Confirmed test files in excluded directories are not affected - Validated lint commands work as expected in both root and solidity packages --------- Co-authored-by: Morteza Shojaei <31728528+mortezashojaei@users.noreply.github.com> --- .changeset/red-games-agree.md | 6 ++ eslint.config.mjs | 98 ++++++++++++++++++- solidity/eslint.config.mjs | 12 +++ solidity/package.json | 1 + .../sdk/src/ism/metadata/aggregation.test.ts | 1 - .../sdk/src/ism/metadata/multisig.test.ts | 1 - typescript/sdk/src/warp/WarpCore.test.ts | 1 - yarn.lock | 1 + 8 files changed, 114 insertions(+), 7 deletions(-) create mode 100644 .changeset/red-games-agree.md create mode 100644 solidity/eslint.config.mjs diff --git a/.changeset/red-games-agree.md b/.changeset/red-games-agree.md new file mode 100644 index 0000000000..a746b1aaca --- /dev/null +++ b/.changeset/red-games-agree.md @@ -0,0 +1,6 @@ +--- +'@hyperlane-xyz/sdk': patch +'@hyperlane-xyz/core': patch +--- + +Added ESLint configuration and dependency to enforce Node.js module restrictions diff --git a/eslint.config.mjs b/eslint.config.mjs index 3be951d450..ec37ff4eda 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -82,10 +82,6 @@ export default [ name: 'console', message: 'Please use a logger and/or the utils package assert', }, - { - name: 'fs', - message: 'Avoid use of node-specific libraries', - }, ], '@typescript-eslint/ban-ts-comment': ['off'], @@ -112,4 +108,98 @@ export default [ 'jest/valid-expect': 'error', }, }, + + { + files: ['**/*.ts', '**/*.js', '**/*.mjs'], + ignores: ['**/aws/**/*', '**/test/**/*', '**/*.test.ts'], + rules: { + 'no-restricted-imports': [ + 'error', + { + paths: [ + { + name: 'fs', + message: + 'Avoid Node.js built-in modules in cross-platform code. Use environment-agnostic alternatives.', + }, + { + name: 'path', + message: + 'Avoid Node.js built-in modules in cross-platform code. Use environment-agnostic alternatives.', + }, + { + name: 'child_process', + message: + 'Avoid Node.js built-in modules in cross-platform code. Use environment-agnostic alternatives.', + }, + { + name: 'os', + message: + 'Avoid Node.js built-in modules in cross-platform code. Use environment-agnostic alternatives.', + }, + { + name: 'process', + message: + 'Avoid Node.js built-in modules in cross-platform code. Use environment-agnostic alternatives.', + }, + { + name: 'http', + message: + 'Avoid Node.js built-in modules in cross-platform code. Use environment-agnostic alternatives.', + }, + { + name: 'https', + message: + 'Avoid Node.js built-in modules in cross-platform code. Use environment-agnostic alternatives.', + }, + { + name: 'net', + message: + 'Avoid Node.js built-in modules in cross-platform code. Use environment-agnostic alternatives.', + }, + { + name: 'dgram', + message: + 'Avoid Node.js built-in modules in cross-platform code. Use environment-agnostic alternatives.', + }, + { + name: 'dns', + message: + 'Avoid Node.js built-in modules in cross-platform code. Use environment-agnostic alternatives.', + }, + { + name: 'crypto', + message: + 'Avoid Node.js built-in modules in cross-platform code. Use environment-agnostic alternatives.', + }, + { + name: 'tls', + message: + 'Avoid Node.js built-in modules in cross-platform code. Use environment-agnostic alternatives.', + }, + { + name: 'cluster', + message: + 'Avoid Node.js built-in modules in cross-platform code. Use environment-agnostic alternatives.', + }, + { + name: 'stream', + message: + 'Avoid Node.js built-in modules in cross-platform code. Use environment-agnostic alternatives.', + }, + { + name: 'vm', + message: + 'Avoid Node.js built-in modules in cross-platform code. Use environment-agnostic alternatives.', + }, + { + name: 'readline', + message: + 'Avoid Node.js built-in modules in cross-platform code. Use environment-agnostic alternatives.', + }, + ], + }, + ], + }, + }, ]; diff --git a/solidity/eslint.config.mjs b/solidity/eslint.config.mjs new file mode 100644 index 0000000000..e323f60701 --- /dev/null +++ b/solidity/eslint.config.mjs @@ -0,0 +1,12 @@ +import MonorepoDefaults from '../eslint.config.mjs'; + +export default [ + ...MonorepoDefaults, + { + ignores: [ + './test/**/*', + './dist/**/*', + '.solcover.js', + ], + }, +]; diff --git a/solidity/package.json b/solidity/package.json index 755cf5fbbb..c367e7b931 100644 --- a/solidity/package.json +++ b/solidity/package.json @@ -20,6 +20,7 @@ "@typechain/hardhat": "^9.1.0", "@types/node": "^18.14.5", "chai": "^4.5.0", + "eslint": "^9.15.0", "ethereum-waffle": "^4.0.10", "ethers": "^5.7.2", "hardhat": "^2.22.2", diff --git a/typescript/sdk/src/ism/metadata/aggregation.test.ts b/typescript/sdk/src/ism/metadata/aggregation.test.ts index 865c3852d4..c1cdfa8141 100644 --- a/typescript/sdk/src/ism/metadata/aggregation.test.ts +++ b/typescript/sdk/src/ism/metadata/aggregation.test.ts @@ -1,4 +1,3 @@ -/* eslint-disable no-restricted-imports */ import { expect } from 'chai'; import { ethers } from 'ethers'; import { existsSync, readFileSync, readdirSync } from 'fs'; diff --git a/typescript/sdk/src/ism/metadata/multisig.test.ts b/typescript/sdk/src/ism/metadata/multisig.test.ts index c0bc3df9b6..b40397b5d3 100644 --- a/typescript/sdk/src/ism/metadata/multisig.test.ts +++ b/typescript/sdk/src/ism/metadata/multisig.test.ts @@ -1,4 +1,3 @@ -/* eslint-disable no-restricted-imports */ import { expect } from 'chai'; import { existsSync, readFileSync, readdirSync } from 'fs'; diff --git a/typescript/sdk/src/warp/WarpCore.test.ts b/typescript/sdk/src/warp/WarpCore.test.ts index f3325a4340..5f16723fbc 100644 --- a/typescript/sdk/src/warp/WarpCore.test.ts +++ b/typescript/sdk/src/warp/WarpCore.test.ts @@ -1,4 +1,3 @@ -/* eslint-disable no-restricted-imports */ import { expect } from 'chai'; import fs from 'fs'; import sinon from 'sinon'; diff --git a/yarn.lock b/yarn.lock index 60dd5e16b7..a3575df63f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7379,6 +7379,7 @@ __metadata: "@typechain/hardhat": "npm:^9.1.0" "@types/node": "npm:^18.14.5" chai: "npm:^4.5.0" + eslint: "npm:^9.15.0" ethereum-waffle: "npm:^4.0.10" ethers: "npm:^5.7.2" fx-portal: "npm:^1.0.3" From ac984a17bb324ab9f7781405448ac8a769052d54 Mon Sep 17 00:00:00 2001 From: ljankovic-txfusion <131957285+ljankovic-txfusion@users.noreply.github.com> Date: Fri, 31 Jan 2025 16:11:19 +0100 Subject: [PATCH 10/11] fix: filter undefined factory addresses (#5340) ### Description When deploying Hyperlane contracts using `warp deploy`, the core addresses obtained using older version of `core deploy` may not include addresses of newer factories (`staticMessageIdWeightedMultisigIsmFactory` and `staticMessageIdWeightedMultisigIsmFactory`). These `undefined` addresses would cause warp deploy to fail, even though they aren't absolutely necessary for warping. This fix handles undefined factory addresses that are filtered out during `warp deploy`. This way, warp deploy can proceed the weighted factories, and users won't have to pay gas fees for core deployment once again to. Attached LogX chain core deployment lacking weighted factory addresses & the resulting error message from `warp deploy` ![2025-01-30 16 30 55](https://github.com/user-attachments/assets/cbe86080-a944-487f-a8d7-beb5f746b007) ![2025-01-30 16 30 39](https://github.com/user-attachments/assets/b118fe99-4894-43bf-a17b-6561d3052d31) ### Drive-by changes - Added `undefined` address filtering in the `filterAddressesMap` function ### Related issues None ### Backward compatibility Yes ### Testing Manual --- .changeset/late-fishes-end.md | 5 ++++ typescript/sdk/src/contracts/contracts.ts | 31 +++++++++++++++++++++-- 2 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 .changeset/late-fishes-end.md diff --git a/.changeset/late-fishes-end.md b/.changeset/late-fishes-end.md new file mode 100644 index 0000000000..1fe798f3cf --- /dev/null +++ b/.changeset/late-fishes-end.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/sdk': patch +--- + +Fix contract address filtering to remove undefined factory addresses from the addresses map diff --git a/typescript/sdk/src/contracts/contracts.ts b/typescript/sdk/src/contracts/contracts.ts index df235c7e3f..ee79bc1e0b 100644 --- a/typescript/sdk/src/contracts/contracts.ts +++ b/typescript/sdk/src/contracts/contracts.ts @@ -1,4 +1,4 @@ -import { Contract } from 'ethers'; +import { Contract, constants } from 'ethers'; import { Ownable, Ownable__factory } from '@hyperlane-xyz/core'; import { @@ -12,6 +12,7 @@ import { objMap, pick, promiseObjAll, + rootLogger, } from '@hyperlane-xyz/utils'; import { ChainMetadataManager } from '../metadata/ChainMetadataManager.js'; @@ -62,9 +63,35 @@ export function filterAddressesMap( const pickedAddressesMap = objMap(addressesMap, (_, addresses) => pick(addresses, factoryKeys), ); + + const chainsWithMissingAddresses = new Set(); + const filledAddressesMap = objMap( + pickedAddressesMap, + (chainName, addresses) => + objMap(addresses, (key, value) => { + if (!value) { + rootLogger.warn( + `Missing address for contract "${key}" on chain ${chainName}`, + ); + chainsWithMissingAddresses.add(chainName); + return constants.AddressZero; + } + return value; + }), + ); + // Add summary warning if any addresses were missing + if (chainsWithMissingAddresses.size > 0) { + rootLogger.warn( + `Warning: Core deployment incomplete for chain(s): ${Array.from( + chainsWithMissingAddresses, + ).join(', ')}. ` + + `Please run 'core deploy' again for these chains to fix the deployment.`, + ); + } + // Filter out chains for which we do not have a complete set of addresses return objFilter( - pickedAddressesMap, + filledAddressesMap, (_, addresses): addresses is HyperlaneAddresses => { return Object.keys(addresses).every((a) => factoryKeys.includes(a)); }, From 76413896a47b9072f0dea1a9ff901d37e1fb4673 Mon Sep 17 00:00:00 2001 From: Lee <6251863+ltyu@users.noreply.github.com> Date: Fri, 31 Jan 2025 12:29:54 -0500 Subject: [PATCH 11/11] feat: replace Getters with Registry Warp Config (#5344) ### Description This PR replaces all non-AW managed Getters with Registry configs ### Related issues - Fixes https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/5239 - Fixes https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/5272 ### Backward compatibility Yes ### Testing Tested with all - `yarn tsx ./typescript/infra/scripts/check/check-deploy.ts -e mainnet3 -m warp --warpRouteId WETH/artela-base` - `yarn tsx ./scripts/warp-routes/generate-warp-config.ts -e mainnet3 --warpRouteId WETH/artela-base` - `yarn tsx ./typescript/infra/scripts/check/check-warp-deploy.ts -e mainnet3 -m warp` - `yarn test:unit` --- ...bitrumEthereumZircuitAmphrETHWarpConfig.ts | 61 --------------- .../getArbitrumNeutronEclipWarpConfig.ts | 47 ----------- .../getArtelaBaseSolanaARTWarpConfig.ts | 52 ------------- .../getArtelaBaseUSDCWarpConfig.ts | 38 --------- .../getArtelaBaseWETHWarpConfig.ts | 38 --------- .../getBaseFormAIXBTWarpConfig.ts | 38 --------- .../getBaseFormGAMEWarpConfig.ts | 38 --------- .../getBaseSolanamainnetTONYWarpConfig.ts | 46 ----------- .../getBscEthereumLumiaPNDRWarpConfig.ts | 48 ------------ .../getEclipseEthereumApxETHWarpConfig.ts | 46 ----------- .../getEclipseEthereumWeETHsWarpConfig.ts | 47 ----------- .../getEthereumBscLumiaLUMIAWarpConfig.ts | 54 ------------- .../getEthereumFlowCbBTCWarpConfig.ts | 50 ------------ .../getEthereumFormUSDCWarpConfig.ts | 48 ------------ .../getEthereumFormUSDTWarpConfig.ts | 45 ----------- .../getEthereumFormWBTCWarpConfig.ts | 38 --------- .../getEthereumFormWSTETHWarpConfig.ts | 38 --------- .../getEthereumSeiFastUSDWarpConfig.ts | 52 ------------- .../getEthereumSeiPumpBTCWarpConfig.ts | 53 ------------- .../getEthereumZircuitRstETHWarpConfig.ts | 39 ---------- .../configGetters/getSuperseedWarpConfigs.ts | 55 ------------- typescript/infra/config/warp.ts | 78 ++++++------------- typescript/infra/test/warp-configs.test.ts | 18 ++++- 23 files changed, 40 insertions(+), 1027 deletions(-) delete mode 100644 typescript/infra/config/environments/mainnet3/warp/configGetters/getArbitrumEthereumZircuitAmphrETHWarpConfig.ts delete mode 100644 typescript/infra/config/environments/mainnet3/warp/configGetters/getArbitrumNeutronEclipWarpConfig.ts delete mode 100644 typescript/infra/config/environments/mainnet3/warp/configGetters/getArtelaBaseSolanaARTWarpConfig.ts delete mode 100644 typescript/infra/config/environments/mainnet3/warp/configGetters/getArtelaBaseUSDCWarpConfig.ts delete mode 100644 typescript/infra/config/environments/mainnet3/warp/configGetters/getArtelaBaseWETHWarpConfig.ts delete mode 100644 typescript/infra/config/environments/mainnet3/warp/configGetters/getBaseFormAIXBTWarpConfig.ts delete mode 100644 typescript/infra/config/environments/mainnet3/warp/configGetters/getBaseFormGAMEWarpConfig.ts delete mode 100644 typescript/infra/config/environments/mainnet3/warp/configGetters/getBaseSolanamainnetTONYWarpConfig.ts delete mode 100644 typescript/infra/config/environments/mainnet3/warp/configGetters/getBscEthereumLumiaPNDRWarpConfig.ts delete mode 100644 typescript/infra/config/environments/mainnet3/warp/configGetters/getEclipseEthereumApxETHWarpConfig.ts delete mode 100644 typescript/infra/config/environments/mainnet3/warp/configGetters/getEclipseEthereumWeETHsWarpConfig.ts delete mode 100644 typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumBscLumiaLUMIAWarpConfig.ts delete mode 100644 typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumFlowCbBTCWarpConfig.ts delete mode 100644 typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumFormUSDCWarpConfig.ts delete mode 100644 typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumFormUSDTWarpConfig.ts delete mode 100644 typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumFormWBTCWarpConfig.ts delete mode 100644 typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumFormWSTETHWarpConfig.ts delete mode 100644 typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumSeiFastUSDWarpConfig.ts delete mode 100644 typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumSeiPumpBTCWarpConfig.ts delete mode 100644 typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumZircuitRstETHWarpConfig.ts delete mode 100644 typescript/infra/config/environments/mainnet3/warp/configGetters/getSuperseedWarpConfigs.ts diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getArbitrumEthereumZircuitAmphrETHWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getArbitrumEthereumZircuitAmphrETHWarpConfig.ts deleted file mode 100644 index 14205f73e1..0000000000 --- a/typescript/infra/config/environments/mainnet3/warp/configGetters/getArbitrumEthereumZircuitAmphrETHWarpConfig.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { ethers } from 'ethers'; - -import { - ChainMap, - HypTokenRouterConfig, - OwnableConfig, - TokenType, -} from '@hyperlane-xyz/sdk'; - -import { getOwnerConfigForAddress } from '../../../../../src/config/environment.js'; -import { - RouterConfigWithoutOwner, - tokens, -} from '../../../../../src/config/warp.js'; - -// MEV Capital -const arbitrumOwner = '0x008615770B588633265cB01Abd19740fAe67d0B9'; -const ethereumOwner = '0x008615770B588633265cB01Abd19740fAe67d0B9'; -const zircuitOwner = '0xD0673e7F3FB4037CA79F53d2d311D0e017d39963'; - -export const getArbitrumEthereumZircuitAmphrETHWarpConfig = async ( - routerConfig: ChainMap, - _abacusWorksEnvOwnerConfig: ChainMap, -): Promise> => { - const arbitrum: HypTokenRouterConfig = { - ...routerConfig.arbitrum, - owner: arbitrumOwner, - proxyAdmin: { - owner: arbitrumOwner, - }, - type: TokenType.synthetic, - interchainSecurityModule: ethers.constants.AddressZero, - }; - - const ethereum: HypTokenRouterConfig = { - ...routerConfig.ethereum, - owner: ethereumOwner, - proxyAdmin: { - owner: ethereumOwner, - }, - type: TokenType.collateral, - token: tokens.ethereum.amphrETH, - interchainSecurityModule: ethers.constants.AddressZero, - }; - - const zircuit: HypTokenRouterConfig = { - ...routerConfig.zircuit, - owner: zircuitOwner, - proxyAdmin: { - owner: zircuitOwner, - }, - type: TokenType.synthetic, - interchainSecurityModule: ethers.constants.AddressZero, - }; - - return { - arbitrum, - ethereum, - zircuit, - }; -}; diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getArbitrumNeutronEclipWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getArbitrumNeutronEclipWarpConfig.ts deleted file mode 100644 index ad6b921951..0000000000 --- a/typescript/infra/config/environments/mainnet3/warp/configGetters/getArbitrumNeutronEclipWarpConfig.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { - ChainMap, - HypTokenRouterConfig, - OwnableConfig, - TokenType, -} from '@hyperlane-xyz/sdk'; - -import { getOwnerConfigForAddress } from '../../../../../src/config/environment.js'; -import { RouterConfigWithoutOwner } from '../../../../../src/config/warp.js'; - -// Eclipse Fi team -const arbitrumOwner = '0xfF07222cb0AC905304d6586Aabf13f497C07F0C8'; -const neutronOwner = - 'neutron1aud8lty0wwmyc86ugkzqrusnrku0ckm0ym62v4ve0jjjyepjjg6spssrwj'; - -export const getArbitrumNeutronEclipWarpConfig = async ( - routerConfig: ChainMap, - _abacusWorksEnvOwnerConfig: ChainMap, -): Promise> => { - const neutronRouter = - '6b04c49fcfd98bc4ea9c05cd5790462a39537c00028333474aebe6ddf20b73a3'; - - const neutron: HypTokenRouterConfig = { - ...routerConfig.neutron, - ...getOwnerConfigForAddress(neutronOwner), - type: TokenType.collateral, - token: 'factory/neutron10sr06r3qkhn7xzpw3339wuj77hu06mzna6uht0/eclip', - foreignDeployment: neutronRouter, - }; - - const arbitrum: HypTokenRouterConfig = { - ...routerConfig.arbitrum, - ...getOwnerConfigForAddress(arbitrumOwner), - type: TokenType.synthetic, - name: 'Eclipse Fi', - symbol: 'ECLIP', - decimals: 6, - totalSupply: 0, - gas: 600_000, - interchainSecurityModule: '0x676151bFB8D29690a359F99AE764860595504689', // This has diverged from the default ism on neutron, we cannot change as it is owned by the Eclip team - }; - - return { - neutron, - arbitrum, - }; -}; diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getArtelaBaseSolanaARTWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getArtelaBaseSolanaARTWarpConfig.ts deleted file mode 100644 index 15e1e71034..0000000000 --- a/typescript/infra/config/environments/mainnet3/warp/configGetters/getArtelaBaseSolanaARTWarpConfig.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { ethers } from 'ethers'; - -import { - ChainMap, - HypTokenRouterConfig, - OwnableConfig, - TokenType, -} from '@hyperlane-xyz/sdk'; - -import { RouterConfigWithoutOwner } from '../../../../../src/config/warp.js'; -import { SEALEVEL_WARP_ROUTE_HANDLER_GAS_AMOUNT } from '../consts.js'; - -// Artela MPC wallt -const EVM_OWNER = '0x801e8135867D65e742eb070A9fC0aD9c2f69B4cd'; -// Artela Squad vault -const SOLANA_OWNER = 'G4ekReWuTheawZ2DNw5k5iA8pGACt7auKwQeEcGi6GWj'; - -// Default ISM -const ISM_CONFIG = ethers.constants.AddressZero; - -export const getArtelaBaseSolanaARTWarpConfig = async ( - routerConfig: ChainMap, - _abacusWorksEnvOwnerConfig: ChainMap, -): Promise> => { - const artela: HypTokenRouterConfig = { - mailbox: routerConfig.artela.mailbox, - owner: EVM_OWNER, - type: TokenType.native, - interchainSecurityModule: ISM_CONFIG, - }; - - const base: HypTokenRouterConfig = { - mailbox: routerConfig.base.mailbox, - owner: EVM_OWNER, - type: TokenType.synthetic, - interchainSecurityModule: ISM_CONFIG, - }; - - const solanamainnet: HypTokenRouterConfig = { - mailbox: routerConfig.solanamainnet.mailbox, - type: TokenType.synthetic, - owner: SOLANA_OWNER, - foreignDeployment: 'ELAJhVNCRfipNT99YTfPBGTAgyD5x9mEv3DYr9fvRM2C', - gas: SEALEVEL_WARP_ROUTE_HANDLER_GAS_AMOUNT, - }; - - return { - artela, - base, - solanamainnet, - }; -}; diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getArtelaBaseUSDCWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getArtelaBaseUSDCWarpConfig.ts deleted file mode 100644 index 43f1238608..0000000000 --- a/typescript/infra/config/environments/mainnet3/warp/configGetters/getArtelaBaseUSDCWarpConfig.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { ethers } from 'ethers'; - -import { ChainMap, HypTokenRouterConfig, TokenType } from '@hyperlane-xyz/sdk'; - -import { - RouterConfigWithoutOwner, - tokens, -} from '../../../../../src/config/warp.js'; - -const artelaOwner = '0x801e8135867D65e742eb070A9fC0aD9c2f69B4cd'; -const baseOwner = '0x801e8135867D65e742eb070A9fC0aD9c2f69B4cd'; - -const ISM_CONFIG = ethers.constants.AddressZero; // Default ISM - -export const getArtelaBaseUSDCWarpConfig = async ( - routerConfig: ChainMap, -): Promise> => { - const artela: HypTokenRouterConfig = { - ...routerConfig.artela, - owner: artelaOwner, - type: TokenType.synthetic, - symbol: 'USDC.a', - interchainSecurityModule: ISM_CONFIG, - }; - - const base: HypTokenRouterConfig = { - ...routerConfig.base, - owner: baseOwner, - type: TokenType.collateral, - token: tokens.base.USDC, - interchainSecurityModule: ISM_CONFIG, - }; - - return { - artela, - base, - }; -}; diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getArtelaBaseWETHWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getArtelaBaseWETHWarpConfig.ts deleted file mode 100644 index 06756c06e4..0000000000 --- a/typescript/infra/config/environments/mainnet3/warp/configGetters/getArtelaBaseWETHWarpConfig.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { ethers } from 'ethers'; - -import { ChainMap, HypTokenRouterConfig, TokenType } from '@hyperlane-xyz/sdk'; - -import { - RouterConfigWithoutOwner, - tokens, -} from '../../../../../src/config/warp.js'; - -const artelaOwner = '0x801e8135867D65e742eb070A9fC0aD9c2f69B4cd'; -const baseOwner = '0x801e8135867D65e742eb070A9fC0aD9c2f69B4cd'; - -const ISM_CONFIG = ethers.constants.AddressZero; // Default ISM - -export const getArtelaBaseWETHWarpConfig = async ( - routerConfig: ChainMap, -): Promise> => { - const artela: HypTokenRouterConfig = { - ...routerConfig.artela, - owner: artelaOwner, - type: TokenType.synthetic, - symbol: 'WETH.a', - interchainSecurityModule: ISM_CONFIG, - }; - - const base: HypTokenRouterConfig = { - ...routerConfig.base, - owner: baseOwner, - type: TokenType.collateral, - token: tokens.base.WETH, - interchainSecurityModule: ISM_CONFIG, - }; - - return { - artela, - base, - }; -}; diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getBaseFormAIXBTWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getBaseFormAIXBTWarpConfig.ts deleted file mode 100644 index 48fac9ebe0..0000000000 --- a/typescript/infra/config/environments/mainnet3/warp/configGetters/getBaseFormAIXBTWarpConfig.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { ethers } from 'ethers'; - -import { ChainMap, HypTokenRouterConfig, TokenType } from '@hyperlane-xyz/sdk'; -import { Address } from '@hyperlane-xyz/utils'; - -import { - RouterConfigWithoutOwner, - tokens, -} from '../../../../../src/config/warp.js'; - -const formSafes: ChainMap
= { - base: '0xFCdf33C6461fE8476AA0b7aC92D631d58c4e0d84', - form: '0x41B624412B529409A437f08Ef80bCabE81053650', -}; - -export const getBaseFormAIXBTWarpConfig = async ( - routerConfig: ChainMap, -): Promise> => { - const base: HypTokenRouterConfig = { - ...routerConfig.base, - owner: formSafes.base, - type: TokenType.collateral, - token: tokens.base.AIXBT, - interchainSecurityModule: ethers.constants.AddressZero, - }; - - const form: HypTokenRouterConfig = { - ...routerConfig.form, - owner: formSafes.form, - type: TokenType.synthetic, - interchainSecurityModule: ethers.constants.AddressZero, - }; - - return { - form, - base, - }; -}; diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getBaseFormGAMEWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getBaseFormGAMEWarpConfig.ts deleted file mode 100644 index c6d071e62b..0000000000 --- a/typescript/infra/config/environments/mainnet3/warp/configGetters/getBaseFormGAMEWarpConfig.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { ethers } from 'ethers'; - -import { ChainMap, HypTokenRouterConfig, TokenType } from '@hyperlane-xyz/sdk'; -import { Address } from '@hyperlane-xyz/utils'; - -import { - RouterConfigWithoutOwner, - tokens, -} from '../../../../../src/config/warp.js'; - -const formSafes: ChainMap
= { - base: '0xFCdf33C6461fE8476AA0b7aC92D631d58c4e0d84', - form: '0x41B624412B529409A437f08Ef80bCabE81053650', -}; - -export const getBaseFormGAMEWarpConfig = async ( - routerConfig: ChainMap, -): Promise> => { - const base: HypTokenRouterConfig = { - ...routerConfig.base, - owner: formSafes.base, - type: TokenType.collateral, - token: tokens.base.GAME, - interchainSecurityModule: ethers.constants.AddressZero, - }; - - const form: HypTokenRouterConfig = { - ...routerConfig.form, - owner: formSafes.form, - type: TokenType.synthetic, - interchainSecurityModule: ethers.constants.AddressZero, - }; - - return { - form, - base, - }; -}; diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getBaseSolanamainnetTONYWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getBaseSolanamainnetTONYWarpConfig.ts deleted file mode 100644 index 6e51bcad6a..0000000000 --- a/typescript/infra/config/environments/mainnet3/warp/configGetters/getBaseSolanamainnetTONYWarpConfig.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { ethers } from 'ethers'; - -import { - ChainMap, - HypTokenRouterConfig, - OwnableConfig, - TokenType, -} from '@hyperlane-xyz/sdk'; - -import { - RouterConfigWithoutOwner, - tokens, -} from '../../../../../src/config/warp.js'; -import { SEALEVEL_WARP_ROUTE_HANDLER_GAS_AMOUNT } from '../consts.js'; - -// Cod3x SAFE -const baseOwner = '0xfEfcb2fb19b9A70B30646Fdc1A0860Eb12F7ff8b'; -// Cod3x Squads vault -const solanamainnetOwner = '7dRAVvdmV3dy4JieuRAirBQ9oSpYaHgmYwupoK5YZcFR'; - -export async function getBaseSolanamainnetTONYWarpConfig( - routerConfig: ChainMap, - _abacusWorksEnvOwnerConfig: ChainMap, -): Promise> { - let base: HypTokenRouterConfig = { - mailbox: routerConfig.base.mailbox, - owner: baseOwner, - type: TokenType.collateral, - token: tokens.base.TONY, - interchainSecurityModule: ethers.constants.AddressZero, - }; - - const solanamainnet: HypTokenRouterConfig = { - mailbox: routerConfig.solanamainnet.mailbox, - owner: solanamainnetOwner, - type: TokenType.synthetic, - foreignDeployment: 'Fa4zQJCH7id5KL1eFJt2mHyFpUNfCCSkHgtMrLvrRJBN', - gas: SEALEVEL_WARP_ROUTE_HANDLER_GAS_AMOUNT, - interchainSecurityModule: ethers.constants.AddressZero, - }; - - return { - base, - solanamainnet, - }; -} diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getBscEthereumLumiaPNDRWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getBscEthereumLumiaPNDRWarpConfig.ts deleted file mode 100644 index 263e13fc76..0000000000 --- a/typescript/infra/config/environments/mainnet3/warp/configGetters/getBscEthereumLumiaPNDRWarpConfig.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { ethers } from 'ethers'; - -import { ChainMap, HypTokenRouterConfig, TokenType } from '@hyperlane-xyz/sdk'; - -import { - RouterConfigWithoutOwner, - tokens, -} from '../../../../../src/config/warp.js'; - -const owners = { - ethereum: '0x9b948CC7CfC4B67262CbbcC37f9d09B61ea6f0E3', - bsc: '0xA788b57518bBE602ac94CCEE5ae7E4831a546Bfd', - lumiaprism: '0x1C4A50f3E9Bfeb268448D19d0D3fe6d58CB0f7BE', -}; - -const ISM_CONFIG = ethers.constants.AddressZero; // Default ISM - -export const getBscEthereumLumiaPrismPNDRWarpConfig = async ( - routerConfig: ChainMap, -): Promise> => { - const ethereum: HypTokenRouterConfig = { - ...routerConfig.ethereum, - owner: owners.ethereum, - type: TokenType.collateral, - token: tokens.ethereum.PNDR, - interchainSecurityModule: ISM_CONFIG, - }; - - const bsc: HypTokenRouterConfig = { - ...routerConfig.bsc, - owner: owners.bsc, - type: TokenType.synthetic, - interchainSecurityModule: ISM_CONFIG, - }; - - const lumiaprism: HypTokenRouterConfig = { - ...routerConfig.lumiaprism, - owner: owners.lumiaprism, - type: TokenType.synthetic, - interchainSecurityModule: ISM_CONFIG, - }; - - return { - ethereum, - bsc, - lumiaprism, - }; -}; diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getEclipseEthereumApxETHWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getEclipseEthereumApxETHWarpConfig.ts deleted file mode 100644 index b27329279b..0000000000 --- a/typescript/infra/config/environments/mainnet3/warp/configGetters/getEclipseEthereumApxETHWarpConfig.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { ethers } from 'ethers'; - -import { - ChainMap, - HypTokenRouterConfig, - OwnableConfig, - TokenType, -} from '@hyperlane-xyz/sdk'; - -import { getOwnerConfigForAddress } from '../../../../../src/config/environment.js'; -import { - RouterConfigWithoutOwner, - tokens, -} from '../../../../../src/config/warp.js'; -import { SEALEVEL_WARP_ROUTE_HANDLER_GAS_AMOUNT } from '../consts.js'; - -// Redacted / Dinero team -const ethereumOwner = '0xA52Fd396891E7A74b641a2Cb1A6999Fcf56B077e'; -const eclipseOwner = 'BwYL3jLky8oHLeQp1S48cVRfZPmcXtg1V33UPmnZK3Jk'; - -export async function getEclipseEthereumApxEthWarpConfig( - routerConfig: ChainMap, - _abacusWorksEnvOwnerConfig: ChainMap, -): Promise> { - const eclipsemainnet: HypTokenRouterConfig = { - ...routerConfig.eclipsemainnet, - ...getOwnerConfigForAddress(eclipseOwner), - type: TokenType.synthetic, - foreignDeployment: '9pEgj7m2VkwLtJHPtTw5d8vbB7kfjzcXXCRgdwruW7C2', - gas: SEALEVEL_WARP_ROUTE_HANDLER_GAS_AMOUNT, - interchainSecurityModule: ethers.constants.AddressZero, - }; - - let ethereum: HypTokenRouterConfig = { - ...routerConfig.ethereum, - ...getOwnerConfigForAddress(ethereumOwner), - type: TokenType.collateral, - token: tokens.ethereum.apxETH, - interchainSecurityModule: ethers.constants.AddressZero, - }; - - return { - eclipsemainnet, - ethereum, - }; -} diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getEclipseEthereumWeETHsWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getEclipseEthereumWeETHsWarpConfig.ts deleted file mode 100644 index 6ec12367dc..0000000000 --- a/typescript/infra/config/environments/mainnet3/warp/configGetters/getEclipseEthereumWeETHsWarpConfig.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { ethers } from 'ethers'; - -import { - ChainMap, - HypTokenRouterConfig, - OwnableConfig, - TokenType, -} from '@hyperlane-xyz/sdk'; - -import { getOwnerConfigForAddress } from '../../../../../src/config/environment.js'; -import { - RouterConfigWithoutOwner, - tokens, -} from '../../../../../src/config/warp.js'; -import { SEALEVEL_WARP_ROUTE_HANDLER_GAS_AMOUNT } from '../consts.js'; - -// Safe owned by Veda -const ethereumOwner = '0xCEA8039076E35a825854c5C2f85659430b06ec96'; -// Vault owned by Veda -const eclipseOwner = '4Cj1s2ipALjJk9foQV4oDaZYCZwSsVkAShQL1KFVJG9b'; - -export async function getEclipseEthereumWeEthsWarpConfig( - routerConfig: ChainMap, - _abacusWorksEnvOwnerConfig: ChainMap, -): Promise> { - const eclipsemainnet: HypTokenRouterConfig = { - ...routerConfig.eclipsemainnet, - ...getOwnerConfigForAddress(eclipseOwner), - type: TokenType.synthetic, - foreignDeployment: '7Zx4wU1QAw98MfvnPFqRh1oyumek7G5VAX6TKB3U1tcn', - gas: SEALEVEL_WARP_ROUTE_HANDLER_GAS_AMOUNT, - interchainSecurityModule: ethers.constants.AddressZero, - }; - - let ethereum: HypTokenRouterConfig = { - ...routerConfig.ethereum, - ...getOwnerConfigForAddress(ethereumOwner), - type: TokenType.collateral, - token: tokens.ethereum.weETHs, - interchainSecurityModule: ethers.constants.AddressZero, - }; - - return { - eclipsemainnet, - ethereum, - }; -} diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumBscLumiaLUMIAWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumBscLumiaLUMIAWarpConfig.ts deleted file mode 100644 index c38160c5a0..0000000000 --- a/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumBscLumiaLUMIAWarpConfig.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { - ChainMap, - HypTokenRouterConfig, - OwnableConfig, - TokenType, -} from '@hyperlane-xyz/sdk'; - -import { RouterConfigWithoutOwner } from '../../../../../src/config/warp.js'; - -// Lumia Team -const owner = '0x8bBA07Ddc72455b55530C17e6f6223EF6E156863'; - -export const getEthereumBscLUMIAWarpConfig = async ( - routerConfig: ChainMap, - _abacusWorksEnvOwnerConfig: ChainMap, -): Promise> => { - const ethereum: HypTokenRouterConfig = { - ...routerConfig.ethereum, - owner, - proxyAdmin: { - owner, - }, - type: TokenType.collateral, - token: '0xD9343a049D5DBd89CD19DC6BcA8c48fB3a0a42a7', - }; - - const bsc: HypTokenRouterConfig = { - ...routerConfig.bsc, - owner, - proxyAdmin: { - owner, - }, - type: TokenType.synthetic, - }; - - const lumia: HypTokenRouterConfig = { - ...routerConfig.lumia, - owner, - type: TokenType.native, - // As this has been removed from the registry in https://github.com/hyperlane-xyz/hyperlane-registry/pull/348, - // we must specify this explicitly. - mailbox: '0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7', - proxyAdmin: { - owner: owner, - address: '0xBC53dACd8c0ac0d2bAC461479EAaf5519eCC8853', - }, - }; - - return { - ethereum, - bsc, - lumia, - }; -}; diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumFlowCbBTCWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumFlowCbBTCWarpConfig.ts deleted file mode 100644 index e96c5625a3..0000000000 --- a/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumFlowCbBTCWarpConfig.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { ethers } from 'ethers'; - -import { - ChainMap, - HypTokenRouterConfig, - OwnableConfig, - TokenType, -} from '@hyperlane-xyz/sdk'; - -import { - RouterConfigWithoutOwner, - tokens, -} from '../../../../../src/config/warp.js'; - -// Flow team Safe -const ethereumOwner = '0x58C3FB862a4F5f038C24F8506BE378e9415c5B6C'; - -// Flow team Safe -const flowOwner = '0xa507DFccA02727B46cBdC600C57E89b2b55E5330'; - -export const getEthereumFlowCbBTCWarpConfig = async ( - routerConfig: ChainMap, - _abacusWorksEnvOwnerConfig: ChainMap, -): Promise> => { - const ethereum: HypTokenRouterConfig = { - ...routerConfig.ethereum, - owner: ethereumOwner, - proxyAdmin: { - owner: ethereumOwner, - }, - type: TokenType.collateral, - token: tokens.ethereum.cbBTC, - interchainSecurityModule: ethers.constants.AddressZero, - }; - - const flowmainnet: HypTokenRouterConfig = { - ...routerConfig.flowmainnet, - owner: flowOwner, - proxyAdmin: { - owner: flowOwner, - }, - type: TokenType.synthetic, - interchainSecurityModule: ethers.constants.AddressZero, - }; - - return { - ethereum, - flowmainnet, - }; -}; diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumFormUSDCWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumFormUSDCWarpConfig.ts deleted file mode 100644 index 9c4482b714..0000000000 --- a/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumFormUSDCWarpConfig.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { ethers } from 'ethers'; - -import { ChainMap, HypTokenRouterConfig, TokenType } from '@hyperlane-xyz/sdk'; -import { Address } from '@hyperlane-xyz/utils'; - -import { - RouterConfigWithoutOwner, - tokens, -} from '../../../../../src/config/warp.js'; - -// Safes from the FORM team -const safeOwners: ChainMap
= { - ethereum: '0xec5ad23e29203301B2C1a765718Cc1de7A8d3FbF', - form: '0x41B624412B529409A437f08Ef80bCabE81053650', -}; - -export const getEthereumFormUSDCWarpConfig = async ( - routerConfig: ChainMap, -): Promise> => { - const ethereum: HypTokenRouterConfig = { - ...routerConfig.ethereum, - owner: safeOwners.ethereum, - proxyAdmin: { - owner: safeOwners.ethereum, - }, - type: TokenType.collateral, - token: tokens.ethereum.USDC, - interchainSecurityModule: ethers.constants.AddressZero, - }; - - // FiatTokenProxy 0xFBf489bb4783D4B1B2e7D07ba39873Fb8068507D - // MasterMinter 0x9Dec8Dfafcce2d45E8FF8C7792DB1D704AB1dc9D - const form: HypTokenRouterConfig = { - ...routerConfig.form, - owner: safeOwners.form, - proxyAdmin: { - owner: safeOwners.form, - }, - type: TokenType.collateralFiat, - token: '0xFBf489bb4783D4B1B2e7D07ba39873Fb8068507D', - interchainSecurityModule: ethers.constants.AddressZero, - }; - - return { - ethereum, - form, - }; -}; diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumFormUSDTWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumFormUSDTWarpConfig.ts deleted file mode 100644 index 0f2b6c3c28..0000000000 --- a/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumFormUSDTWarpConfig.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { ethers } from 'ethers'; - -import { ChainMap, HypTokenRouterConfig, TokenType } from '@hyperlane-xyz/sdk'; -import { Address } from '@hyperlane-xyz/utils'; - -import { - RouterConfigWithoutOwner, - tokens, -} from '../../../../../src/config/warp.js'; - -// Safes from the FORM team -const safeOwners: ChainMap
= { - ethereum: '0xec5ad23e29203301B2C1a765718Cc1de7A8d3FbF', - form: '0x41B624412B529409A437f08Ef80bCabE81053650', -}; - -export const getEthereumFormUSDTWarpConfig = async ( - routerConfig: ChainMap, -): Promise> => { - const ethereum: HypTokenRouterConfig = { - ...routerConfig.ethereum, - owner: safeOwners.ethereum, - proxyAdmin: { - owner: safeOwners.ethereum, - }, - type: TokenType.collateral, - token: tokens.ethereum.USDT, - interchainSecurityModule: ethers.constants.AddressZero, - }; - - const form: HypTokenRouterConfig = { - ...routerConfig.form, - owner: safeOwners.form, - proxyAdmin: { - owner: safeOwners.form, - }, - type: TokenType.synthetic, - interchainSecurityModule: ethers.constants.AddressZero, - }; - - return { - ethereum, - form, - }; -}; diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumFormWBTCWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumFormWBTCWarpConfig.ts deleted file mode 100644 index c7a1491fd1..0000000000 --- a/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumFormWBTCWarpConfig.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { ethers } from 'ethers'; - -import { ChainMap, HypTokenRouterConfig, TokenType } from '@hyperlane-xyz/sdk'; -import { Address } from '@hyperlane-xyz/utils'; - -import { - RouterConfigWithoutOwner, - tokens, -} from '../../../../../src/config/warp.js'; - -const formSafes: ChainMap
= { - ethereum: '0xec5ad23e29203301B2C1a765718Cc1de7A8d3FbF', - form: '0x41B624412B529409A437f08Ef80bCabE81053650', -}; - -export const getEthereumFormWBTCWarpConfig = async ( - routerConfig: ChainMap, -): Promise> => { - const ethereum: HypTokenRouterConfig = { - ...routerConfig.ethereum, - owner: formSafes.ethereum, - type: TokenType.collateral, - token: tokens.ethereum.WBTC, - interchainSecurityModule: ethers.constants.AddressZero, - }; - - const form: HypTokenRouterConfig = { - ...routerConfig.form, - owner: formSafes.form, - type: TokenType.synthetic, - interchainSecurityModule: ethers.constants.AddressZero, - }; - - return { - form, - ethereum, - }; -}; diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumFormWSTETHWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumFormWSTETHWarpConfig.ts deleted file mode 100644 index b0cce79a35..0000000000 --- a/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumFormWSTETHWarpConfig.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { ethers } from 'ethers'; - -import { ChainMap, HypTokenRouterConfig, TokenType } from '@hyperlane-xyz/sdk'; -import { Address } from '@hyperlane-xyz/utils'; - -import { - RouterConfigWithoutOwner, - tokens, -} from '../../../../../src/config/warp.js'; - -const formSafes: ChainMap
= { - ethereum: '0xec5ad23e29203301B2C1a765718Cc1de7A8d3FbF', - form: '0x41B624412B529409A437f08Ef80bCabE81053650', -}; - -export const getEthereumFormWSTETHWarpConfig = async ( - routerConfig: ChainMap, -): Promise> => { - const ethereum: HypTokenRouterConfig = { - ...routerConfig.ethereum, - owner: formSafes.ethereum, - type: TokenType.collateral, - token: tokens.ethereum.WSTETH, - interchainSecurityModule: ethers.constants.AddressZero, - }; - - const form: HypTokenRouterConfig = { - ...routerConfig.form, - owner: formSafes.form, - type: TokenType.synthetic, - interchainSecurityModule: ethers.constants.AddressZero, - }; - - return { - form, - ethereum, - }; -}; diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumSeiFastUSDWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumSeiFastUSDWarpConfig.ts deleted file mode 100644 index 36b01eb6eb..0000000000 --- a/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumSeiFastUSDWarpConfig.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { ethers } from 'ethers'; - -import { - ChainMap, - HypTokenRouterConfig, - OwnableConfig, - TokenType, -} from '@hyperlane-xyz/sdk'; - -import { - RouterConfigWithoutOwner, - tokens, -} from '../../../../../src/config/warp.js'; - -// Elixir -const owner = '0x00000000F51340906F767C6999Fe512b1275955C'; -const elixirSafe = '0x738744237b7fd97af670d9ddf54390c24263cea8'; - -export const getEthereumSeiFastUSDWarpConfig = async ( - routerConfig: ChainMap, - _abacusWorksEnvOwnerConfig: ChainMap, -): Promise> => { - const sei: HypTokenRouterConfig = { - ...routerConfig.viction, - owner, - proxyAdmin: { - owner, - }, - type: TokenType.XERC20, - name: 'fastUSD', - symbol: 'fastUSD', - decimals: 18, - token: tokens.sei.fastUSD, - interchainSecurityModule: ethers.constants.AddressZero, - }; - - const ethereum: HypTokenRouterConfig = { - ...routerConfig.ethereum, - owner: elixirSafe, - ownerOverrides: { - proxyAdmin: owner, - }, - type: TokenType.collateral, - token: tokens.ethereum.deUSD, - interchainSecurityModule: ethers.constants.AddressZero, - }; - - return { - sei, - ethereum, - }; -}; diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumSeiPumpBTCWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumSeiPumpBTCWarpConfig.ts deleted file mode 100644 index c321f2efcc..0000000000 --- a/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumSeiPumpBTCWarpConfig.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { ethers } from 'ethers'; - -import { - ChainMap, - HypTokenRouterConfig, - OwnableConfig, - TokenType, -} from '@hyperlane-xyz/sdk'; - -import { getOwnerConfigForAddress } from '../../../../../src/config/environment.js'; -import { - RouterConfigWithoutOwner, - tokens, -} from '../../../../../src/config/warp.js'; - -// pumpBTC team -const ethereumOwner = '0x77A0545Dc1Dc6bAee8d9c1d436c6688a75Ae5777'; -const seiOwner = '0x14A359aE2446eaC89495b3F28b7a29cE2A17f392'; - -export const getEthereumSeiPumpBTCWarpConfig = async ( - routerConfig: ChainMap, - _abacusWorksEnvOwnerConfig: ChainMap, -): Promise> => { - const ethereum: HypTokenRouterConfig = { - ...routerConfig.ethereum, - owner: ethereumOwner, - proxyAdmin: { - // Address explicitly specified as move away from the AW proxy admin - address: '0x64d4ba42f033927ca3babbbebaa11ac8caed9472', - owner: ethereumOwner, - }, - type: TokenType.collateral, - token: tokens.ethereum.pumpBTCsei, - interchainSecurityModule: ethers.constants.AddressZero, - }; - - const sei: HypTokenRouterConfig = { - ...routerConfig.sei, - owner: seiOwner, - proxyAdmin: { - // Address explicitly specified as move away from the AW proxy admin - address: '0x932a0a357CbE9a06c0FCec8C56335DA162c5D071', - owner: seiOwner, - }, - type: TokenType.synthetic, - interchainSecurityModule: ethers.constants.AddressZero, - }; - - return { - ethereum, - sei, - }; -}; diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumZircuitRstETHWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumZircuitRstETHWarpConfig.ts deleted file mode 100644 index 755714c719..0000000000 --- a/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumZircuitRstETHWarpConfig.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { ethers } from 'ethers'; - -import { ChainMap, HypTokenRouterConfig, TokenType } from '@hyperlane-xyz/sdk'; - -import { - RouterConfigWithoutOwner, - tokens, -} from '../../../../../src/config/warp.js'; - -const RstETHSafes = { - ethereum: '0xDA0d054265bB30F4f32C92066428FE57513E7ee1', - zircuit: '0xA1895dF8AE7b7678E82E76b167A24c82Fb83ec9A', -}; - -const ISM_CONFIG = ethers.constants.AddressZero; // Default ISM - -export const getEthereumZircuitRstETHWarpConfig = async ( - routerConfig: ChainMap, -): Promise> => { - const ethereum: HypTokenRouterConfig = { - ...routerConfig.ethereum, - owner: RstETHSafes.ethereum, - type: TokenType.collateral, - token: tokens.ethereum.rstETH, - interchainSecurityModule: ISM_CONFIG, - }; - - const zircuit: HypTokenRouterConfig = { - ...routerConfig.zircuit, - owner: RstETHSafes.zircuit, - type: TokenType.synthetic, - interchainSecurityModule: ISM_CONFIG, - }; - - return { - ethereum, - zircuit, - }; -}; diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getSuperseedWarpConfigs.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getSuperseedWarpConfigs.ts deleted file mode 100644 index e10d681579..0000000000 --- a/typescript/infra/config/environments/mainnet3/warp/configGetters/getSuperseedWarpConfigs.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { ethers } from 'ethers'; - -import { ChainMap, HypTokenRouterConfig, TokenType } from '@hyperlane-xyz/sdk'; -import { Address } from '@hyperlane-xyz/utils'; - -import { - RouterConfigWithoutOwner, - tokens, -} from '../../../../../src/config/warp.js'; - -const safeOwners: ChainMap
= { - ethereum: '0x11BEBBf509248735203BAAAe90c1a27EEE70D567', - superseed: '0x6652010BaCE855DF870D427daA6141c313994929', - optimism: '0x0D493D7E51212bbBF0F1ca4bcfA1E5514C7fEF10', -}; - -export const getEthereumSuperseedUSDTConfig = async ( - routerConfig: ChainMap, -): Promise> => { - return { - ethereum: { - ...routerConfig.ethereum, - owner: safeOwners.ethereum, - type: TokenType.collateral, - token: tokens.ethereum.USDT, - interchainSecurityModule: ethers.constants.AddressZero, - }, - superseed: { - ...routerConfig.superseed, - owner: safeOwners.superseed, - type: TokenType.synthetic, - interchainSecurityModule: ethers.constants.AddressZero, - }, - }; -}; - -export const getOptimismSuperseedOPConfig = async ( - routerConfig: ChainMap, -): Promise> => { - return { - optimism: { - ...routerConfig.optimism, - owner: safeOwners.optimism, - type: TokenType.collateral, - token: tokens.optimism.OP, - interchainSecurityModule: ethers.constants.AddressZero, - }, - superseed: { - ...routerConfig.superseed, - owner: safeOwners.superseed, - type: TokenType.synthetic, - interchainSecurityModule: ethers.constants.AddressZero, - }, - }; -}; diff --git a/typescript/infra/config/warp.ts b/typescript/infra/config/warp.ts index 6a9e5499e7..58d3b2f7ae 100644 --- a/typescript/infra/config/warp.ts +++ b/typescript/infra/config/warp.ts @@ -4,7 +4,7 @@ import { MultiProvider, OwnableConfig, } from '@hyperlane-xyz/sdk'; -import { objMap } from '@hyperlane-xyz/utils'; +import { assert, objMap } from '@hyperlane-xyz/utils'; import { EnvironmentConfig, @@ -17,39 +17,20 @@ import { getAppChainBaseUSDCWarpConfig } from './environments/mainnet3/warp/conf import { getArbitrumBaseBlastBscEthereumGnosisMantleModeOptimismPolygonScrollZeroNetworkZoraMainnetETHWarpConfig } from './environments/mainnet3/warp/configGetters/getArbitrumBaseBlastBscEthereumGnosisMantleModeOptimismPolygonScrollZeroNetworkZoraMainnetETHWarpConfig.js'; import { getArbitrumBaseEthereumOptimismPolygonZeroNetworkUSDC } from './environments/mainnet3/warp/configGetters/getArbitrumBaseEthereumOptimismPolygonZeroNetworkUSDCWarpConfig.js'; import { getArbitrumEthereumMantleModePolygonScrollZeroNetworkUSDTWarpConfig } from './environments/mainnet3/warp/configGetters/getArbitrumBscEthereumMantleModePolygonScrollZeronetworkUSDTWarpConfig.js'; -import { getArbitrumEthereumZircuitAmphrETHWarpConfig } from './environments/mainnet3/warp/configGetters/getArbitrumEthereumZircuitAmphrETHWarpConfig.js'; -import { getArbitrumNeutronEclipWarpConfig } from './environments/mainnet3/warp/configGetters/getArbitrumNeutronEclipWarpConfig.js'; import { getArbitrumNeutronTiaWarpConfig } from './environments/mainnet3/warp/configGetters/getArbitrumNeutronTiaWarpConfig.js'; -import { getArtelaBaseSolanaARTWarpConfig } from './environments/mainnet3/warp/configGetters/getArtelaBaseSolanaARTWarpConfig.js'; -import { getArtelaBaseUSDCWarpConfig } from './environments/mainnet3/warp/configGetters/getArtelaBaseUSDCWarpConfig.js'; -import { getArtelaBaseWETHWarpConfig } from './environments/mainnet3/warp/configGetters/getArtelaBaseWETHWarpConfig.js'; -import { getBaseFormAIXBTWarpConfig } from './environments/mainnet3/warp/configGetters/getBaseFormAIXBTWarpConfig.js'; -import { getBaseFormGAMEWarpConfig } from './environments/mainnet3/warp/configGetters/getBaseFormGAMEWarpConfig.js'; import { getTRUMPWarpConfig, getTrumpchainTRUMPWarpConfig, } from './environments/mainnet3/warp/configGetters/getBaseSolanaTRUMPWarpConfig.js'; -import { getBaseSolanamainnetTONYWarpConfig } from './environments/mainnet3/warp/configGetters/getBaseSolanamainnetTONYWarpConfig.js'; import { getBaseZeroNetworkCBBTCWarpConfig } from './environments/mainnet3/warp/configGetters/getBaseZeroNetworkCBBTCWarpConfig.js'; import { getBobaBsquaredSwellUBTCWarpConfig } from './environments/mainnet3/warp/configGetters/getBobaBsquaredSwellUBTCWarpConfig.js'; -import { getBscEthereumLumiaPrismPNDRWarpConfig } from './environments/mainnet3/warp/configGetters/getBscEthereumLumiaPNDRWarpConfig.js'; -import { getEclipseEthereumApxEthWarpConfig } from './environments/mainnet3/warp/configGetters/getEclipseEthereumApxETHWarpConfig.js'; import { getEclipseEthereumSolanaUSDTWarpConfig } from './environments/mainnet3/warp/configGetters/getEclipseEthereumSolanaUSDTWarpConfig.js'; import { getEclipseEthereumWBTCWarpConfig } from './environments/mainnet3/warp/configGetters/getEclipseEthereumWBTCWarpConfig.js'; -import { getEclipseEthereumWeEthsWarpConfig } from './environments/mainnet3/warp/configGetters/getEclipseEthereumWeETHsWarpConfig.js'; import { getEclipseStrideTiaWarpConfig } from './environments/mainnet3/warp/configGetters/getEclipseStrideSTTIAWarpConfig.js'; import { getEclipseStrideStTiaWarpConfig } from './environments/mainnet3/warp/configGetters/getEclipseStrideTIAWarpConfig.js'; -import { getEthereumBscLUMIAWarpConfig } from './environments/mainnet3/warp/configGetters/getEthereumBscLumiaLUMIAWarpConfig.js'; -import { getEthereumFlowCbBTCWarpConfig } from './environments/mainnet3/warp/configGetters/getEthereumFlowCbBTCWarpConfig.js'; -import { getEthereumFormUSDCWarpConfig } from './environments/mainnet3/warp/configGetters/getEthereumFormUSDCWarpConfig.js'; -import { getEthereumFormUSDTWarpConfig } from './environments/mainnet3/warp/configGetters/getEthereumFormUSDTWarpConfig.js'; -import { getEthereumFormWBTCWarpConfig } from './environments/mainnet3/warp/configGetters/getEthereumFormWBTCWarpConfig.js'; -import { getEthereumFormWSTETHWarpConfig } from './environments/mainnet3/warp/configGetters/getEthereumFormWSTETHWarpConfig.js'; import { getEthereumInevmUSDCWarpConfig } from './environments/mainnet3/warp/configGetters/getEthereumInevmUSDCWarpConfig.js'; import { getEthereumInevmUSDTWarpConfig } from './environments/mainnet3/warp/configGetters/getEthereumInevmUSDTWarpConfig.js'; import { getEthereumInkUSDCConfig } from './environments/mainnet3/warp/configGetters/getEthereumInkUSDCWarpConfig.js'; -import { getEthereumSeiFastUSDWarpConfig } from './environments/mainnet3/warp/configGetters/getEthereumSeiFastUSDWarpConfig.js'; -import { getEthereumSeiPumpBTCWarpConfig } from './environments/mainnet3/warp/configGetters/getEthereumSeiPumpBTCWarpConfig.js'; import { getEthereumSuperseedCBBTCWarpConfig, getEthereumSuperseedUSDCWarpConfig, @@ -58,54 +39,39 @@ import { getEthereumVictionETHWarpConfig } from './environments/mainnet3/warp/co import { getEthereumVictionUSDCWarpConfig } from './environments/mainnet3/warp/configGetters/getEthereumVictionUSDCWarpConfig.js'; import { getEthereumVictionUSDTWarpConfig } from './environments/mainnet3/warp/configGetters/getEthereumVictionUSDTWarpConfig.js'; import { getEthereumZircuitRe7LRTWarpConfig } from './environments/mainnet3/warp/configGetters/getEthereumZircuitRe7LRTWarpConfig.js'; -import { getEthereumZircuitRstETHWarpConfig } from './environments/mainnet3/warp/configGetters/getEthereumZircuitRstETHWarpConfig.js'; import { getInevmInjectiveINJWarpConfig } from './environments/mainnet3/warp/configGetters/getInevmInjectiveINJWarpConfig.js'; import { getMantapacificNeutronTiaWarpConfig } from './environments/mainnet3/warp/configGetters/getMantapacificNeutronTiaWarpConfig.js'; import { getRenzoEZETHWarpConfig } from './environments/mainnet3/warp/configGetters/getRenzoEZETHWarpConfig.js'; import { getRenzoPZETHWarpConfig } from './environments/mainnet3/warp/configGetters/getRenzoPZETHWarpConfig.js'; -import { - getEthereumSuperseedUSDTConfig, - getOptimismSuperseedOPConfig, -} from './environments/mainnet3/warp/configGetters/getSuperseedWarpConfigs.js'; import { WarpRouteIds } from './environments/mainnet3/warp/warpIds.js'; +import { getGithubRegistry } from './registry.js'; type WarpConfigGetter = ( routerConfig: ChainMap, abacusWorksEnvOwnerConfig: ChainMap, + warpRouteId: string, ) => Promise>; export const warpConfigGetterMap: Record = { [WarpRouteIds.Ancient8EthereumUSDC]: getAncient8EthereumUSDCWarpConfig, - [WarpRouteIds.ArbitrumEthereumZircuitAMPHRETH]: - getArbitrumEthereumZircuitAmphrETHWarpConfig, [WarpRouteIds.EthereumInevmUSDC]: getEthereumInevmUSDCWarpConfig, [WarpRouteIds.EthereumInevmUSDT]: getEthereumInevmUSDTWarpConfig, - [WarpRouteIds.ArbitrumNeutronEclip]: getArbitrumNeutronEclipWarpConfig, [WarpRouteIds.ArbitrumNeutronTIA]: getArbitrumNeutronTiaWarpConfig, [WarpRouteIds.ArbitrumBaseBlastBscEthereumFraxtalLineaModeOptimismSeiSwellTaikoZircuitEZETH]: getRenzoEZETHWarpConfig, [WarpRouteIds.InevmInjectiveINJ]: getInevmInjectiveINJWarpConfig, - [WarpRouteIds.BaseSolanamainnetTONY]: getBaseSolanamainnetTONYWarpConfig, [WarpRouteIds.ArbitrumAvalancheBaseFlowmainnetFormOptimismSolanamainnetWorldchainTRUMP]: getTRUMPWarpConfig, [WarpRouteIds.SolanamainnetTrumpchainTRUMP]: getTrumpchainTRUMPWarpConfig, - [WarpRouteIds.BscEthereumLumiaPrismPNDR]: - getBscEthereumLumiaPrismPNDRWarpConfig, - [WarpRouteIds.EthereumFlowCbBTC]: getEthereumFlowCbBTCWarpConfig, [WarpRouteIds.EthereumInkUSDC]: getEthereumInkUSDCConfig, - [WarpRouteIds.EthereumSeiFastUSD]: getEthereumSeiFastUSDWarpConfig, - [WarpRouteIds.EthereumSeiPumpBTC]: getEthereumSeiPumpBTCWarpConfig, [WarpRouteIds.EthereumVictionETH]: getEthereumVictionETHWarpConfig, [WarpRouteIds.EthereumVictionUSDC]: getEthereumVictionUSDCWarpConfig, [WarpRouteIds.EthereumVictionUSDT]: getEthereumVictionUSDTWarpConfig, [WarpRouteIds.EthereumSwellZircuitPZETH]: getRenzoPZETHWarpConfig, - [WarpRouteIds.EthereumBscLumiaLUMIA]: getEthereumBscLUMIAWarpConfig, [WarpRouteIds.MantapacificNeutronTIA]: getMantapacificNeutronTiaWarpConfig, - [WarpRouteIds.EclipseEthereumApxEth]: getEclipseEthereumApxEthWarpConfig, [WarpRouteIds.EclipseEthereumSolanaUSDT]: getEclipseEthereumSolanaUSDTWarpConfig, [WarpRouteIds.EclipseEthereumWBTC]: getEclipseEthereumWBTCWarpConfig, - [WarpRouteIds.EclipseEthereumWeETHs]: getEclipseEthereumWeEthsWarpConfig, [WarpRouteIds.BaseZeroNetworkCBBTC]: getBaseZeroNetworkCBBTCWarpConfig, [WarpRouteIds.ArbitrumEthereumMantleModePolygonScrollZeroNetworkUSDT]: getArbitrumEthereumMantleModePolygonScrollZeroNetworkUSDTWarpConfig, @@ -113,7 +79,6 @@ export const warpConfigGetterMap: Record = { getArbitrumBaseEthereumOptimismPolygonZeroNetworkUSDC, [WarpRouteIds.ArbitrumBaseBlastBscEthereumGnosisLiskMantleModeOptimismPolygonScrollZeroNetworkZoraMainnet]: getArbitrumBaseBlastBscEthereumGnosisMantleModeOptimismPolygonScrollZeroNetworkZoraMainnetETHWarpConfig, - [WarpRouteIds.ArtelaBaseSolanaART]: getArtelaBaseSolanaARTWarpConfig, [WarpRouteIds.EclipseStrideTIA]: getEclipseStrideTiaWarpConfig, [WarpRouteIds.EclipseStrideSTTIA]: getEclipseStrideStTiaWarpConfig, [WarpRouteIds.AppchainBaseUSDC]: getAppChainBaseUSDCWarpConfig, @@ -121,19 +86,18 @@ export const warpConfigGetterMap: Record = { [WarpRouteIds.EthereumZircuitRe7LRT]: getEthereumZircuitRe7LRTWarpConfig, [WarpRouteIds.EthereumSuperseedCBBTC]: getEthereumSuperseedCBBTCWarpConfig, [WarpRouteIds.EthereumSuperseedUSDC]: getEthereumSuperseedUSDCWarpConfig, - [WarpRouteIds.EthereumFormUSDT]: getEthereumFormUSDTWarpConfig, - [WarpRouteIds.EthereumFormUSDC]: getEthereumFormUSDCWarpConfig, - [WarpRouteIds.EthereumSuperseedUSDT]: getEthereumSuperseedUSDTConfig, - [WarpRouteIds.OptimismSuperseedOP]: getOptimismSuperseedOPConfig, - [WarpRouteIds.EthereumZircuitRstETH]: getEthereumZircuitRstETHWarpConfig, - [WarpRouteIds.EthereumFormWBTC]: getEthereumFormWBTCWarpConfig, - [WarpRouteIds.EthereumFormWSTETH]: getEthereumFormWSTETHWarpConfig, - [WarpRouteIds.BaseFormAIXBT]: getBaseFormAIXBTWarpConfig, - [WarpRouteIds.BaseFormGAME]: getBaseFormGAMEWarpConfig, - [WarpRouteIds.ArtelaBaseUSDC]: getArtelaBaseUSDCWarpConfig, - [WarpRouteIds.ArtelaBaseWETH]: getArtelaBaseWETHWarpConfig, }; +async function getConfigFromGithub( + _routerConfig: ChainMap, + _abacusWorksEnvOwnerConfig: ChainMap, + warpRouteId: string, +): Promise> { + const warpRoute = await getGithubRegistry().getWarpDeployConfig(warpRouteId); + assert(warpRoute, `Warp route Config not found for ${warpRouteId}`); + return warpRoute; +} + export async function getWarpConfig( multiProvider: MultiProvider, envConfig: EnvironmentConfig, @@ -162,13 +126,17 @@ export async function getWarpConfig( }); const warpConfigGetter = warpConfigGetterMap[warpRouteId]; - if (!warpConfigGetter) { - throw new Error( - `Unknown warp route: ${warpRouteId}, must be one of: ${Object.keys( - warpConfigGetterMap, - ).join(', ')}`, + if (warpConfigGetter) { + return warpConfigGetter( + routerConfigWithoutOwner, + abacusWorksEnvOwnerConfig, + warpRouteId, ); } - return warpConfigGetter(routerConfigWithoutOwner, abacusWorksEnvOwnerConfig); + return getConfigFromGithub( + routerConfigWithoutOwner, + abacusWorksEnvOwnerConfig, + warpRouteId, + ); } diff --git a/typescript/infra/test/warp-configs.test.ts b/typescript/infra/test/warp-configs.test.ts index 8144e23810..b56fa57e38 100644 --- a/typescript/infra/test/warp-configs.test.ts +++ b/typescript/infra/test/warp-configs.test.ts @@ -1,4 +1,5 @@ -import { expect } from 'chai'; +import * as chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; import { MultiProvider } from '@hyperlane-xyz/sdk'; import { diffObjMerge } from '@hyperlane-xyz/utils'; @@ -10,7 +11,11 @@ import { getHyperlaneCore, } from '../scripts/core-utils.js'; +const { expect } = chai; +chai.use(chaiAsPromised); +chai.should(); const DEFAULT_TIMEOUT = 60000; + describe('Warp Configs', async function () { this.timeout(DEFAULT_TIMEOUT); const ENV = 'mainnet3'; @@ -47,4 +52,15 @@ describe('Warp Configs', async function () { ).to.be.false; }); } + + it('should throw if warpRouteId is not found in either Getter nor Registry', async () => { + const invalidWarpIds = '1111bla-bla-bla111'; + await getWarpConfig( + multiProvider, + envConfig, + invalidWarpIds, + ).should.eventually.be.rejectedWith( + `Warp route Config not found for ${invalidWarpIds}`, + ); + }); });