From dac2fb52103aea9c9b0a064b623995120c0941da Mon Sep 17 00:00:00 2001 From: Daniel Savu <23065004+daniel-savu@users.noreply.github.com> Date: Fri, 23 Aug 2024 20:39:30 +0100 Subject: [PATCH] feat: SOL (solana <> eclipse) warp route (#4268) ### Description ### Drive-by changes ### Related issues ### Backward compatibility ### Testing --------- Co-authored-by: Trevor Porter --- .github/workflows/test.yml | 5 + rust/sealevel/client/src/cmd_utils.rs | 52 +++--- rust/sealevel/client/src/context.rs | 1 + rust/sealevel/client/src/core.rs | 57 ++++++- rust/sealevel/client/src/igp.rs | 12 +- rust/sealevel/client/src/main.rs | 3 +- rust/sealevel/client/src/multisig_ism.rs | 22 ++- rust/sealevel/client/src/router.rs | 10 +- rust/sealevel/client/src/warp_route.rs | 150 ++++++++++++++++-- .../solana/hyperlane/program-ids.json | 3 + .../mainnet3/solana/core/program-ids.json | 2 +- .../warp-routes/eclipsesol/program-ids.json | 10 ++ .../warp-routes/eclipsesol/token-config.json | 15 ++ .../warp-routes/pzeth/program-ids.json | 10 ++ .../warp-routes/pzeth/token-config.json | 18 +++ .../libraries/multisig-ism/src/test_data.rs | 25 +-- .../hyperlane-sealevel-token/src/plugin.rs | 10 +- .../multisig-ism-message-id/src/processor.rs | 2 +- rust/utils/run-locally/src/solana.rs | 2 +- .../environments/mainnet3/gasPrices.json | 4 +- .../environments/mainnet3/warp/addresses.json | 11 +- .../getEthereumSolanaPzETHWarpConfig.ts | 34 ++++ .../mainnet3/warp/verification.json | 19 +++ typescript/infra/config/warp.ts | 3 + .../src/providers/transactionFeeEstimators.ts | 2 +- .../sdk/src/router/GasRouterDeployer.ts | 2 +- 26 files changed, 419 insertions(+), 65 deletions(-) create mode 100644 rust/sealevel/environments/mainnet3/multisig-ism-message-id/solana/hyperlane/program-ids.json create mode 100644 rust/sealevel/environments/mainnet3/warp-routes/eclipsesol/program-ids.json create mode 100644 rust/sealevel/environments/mainnet3/warp-routes/eclipsesol/token-config.json create mode 100644 rust/sealevel/environments/mainnet3/warp-routes/pzeth/program-ids.json create mode 100644 rust/sealevel/environments/mainnet3/warp-routes/pzeth/token-config.json create mode 100644 typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumSolanaPzETHWarpConfig.ts diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index addd56bc2a..91c208f960 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -246,6 +246,11 @@ jobs: !./rust key: ${{ github.event.pull_request.head.sha || github.sha }} + - name: Install system dependencies + run: | + sudo apt-get update -qq + sudo apt-get install -qq -y libudev-dev pkg-config protobuf-compiler + - name: Checkout registry uses: ./.github/actions/checkout-registry diff --git a/rust/sealevel/client/src/cmd_utils.rs b/rust/sealevel/client/src/cmd_utils.rs index 3fdd7d7e17..8727251121 100644 --- a/rust/sealevel/client/src/cmd_utils.rs +++ b/rust/sealevel/client/src/cmd_utils.rs @@ -13,6 +13,8 @@ use solana_sdk::{ signature::{Keypair, Signer}, }; +const SOLANA_DOMAIN: u32 = 1399811149; + pub(crate) fn account_exists(client: &RpcClient, account: &Pubkey) -> Result { // Using `get_account_with_commitment` instead of `get_account` so we get Ok(None) when the account // doesn't exist, rather than an error @@ -29,10 +31,17 @@ pub(crate) fn deploy_program_idempotent( program_keypair_path: &str, program_path: &str, url: &str, + local_domain: u32, ) -> Result<(), ClientError> { let client = RpcClient::new(url.to_string()); if !account_exists(&client, &program_keypair.pubkey())? { - deploy_program(payer_keypair_path, program_keypair_path, program_path, url); + deploy_program( + payer_keypair_path, + program_keypair_path, + program_path, + url, + local_domain, + ); } else { println!("Program {} already deployed", program_keypair.pubkey()); } @@ -45,25 +54,29 @@ pub(crate) fn deploy_program( program_keypair_path: &str, program_path: &str, url: &str, + local_domain: u32, ) { - build_cmd( - &[ - "solana", - "--url", - url, - "-k", - payer_keypair_path, - "program", - "deploy", - program_path, - "--upgrade-authority", - payer_keypair_path, - "--program-id", - program_keypair_path, - ], - None, - None, - ); + let mut command = vec![ + "solana", + "--url", + url, + "-k", + payer_keypair_path, + "program", + "deploy", + program_path, + "--upgrade-authority", + payer_keypair_path, + "--program-id", + program_keypair_path, + ]; + + if local_domain.eq(&SOLANA_DOMAIN) { + // May need tweaking depending on gas prices / available balance + command.append(&mut vec!["--with-compute-unit-price", "550000"]); + } + + build_cmd(command.as_slice(), None, None); } pub(crate) fn create_new_directory(parent_dir: &Path, name: &str) -> PathBuf { @@ -112,6 +125,7 @@ fn build_cmd(cmd: &[&str], wd: Option<&str>, env: Option<&HashMap<&str, &str>>) if let Some(env) = env { c.envs(env); } + println!("Running command: {:?}", c); let status = c.status().expect("Failed to run command"); assert!( status.success(), diff --git a/rust/sealevel/client/src/context.rs b/rust/sealevel/client/src/context.rs index 5cb9ba383b..e704510a7a 100644 --- a/rust/sealevel/client/src/context.rs +++ b/rust/sealevel/client/src/context.rs @@ -29,6 +29,7 @@ pub(crate) struct Context { pub require_tx_approval: bool, } +#[derive(Debug)] pub(crate) struct InstructionWithDescription { pub instruction: Instruction, pub description: Option, diff --git a/rust/sealevel/client/src/core.rs b/rust/sealevel/client/src/core.rs index 7a9f966437..d5f33abd1c 100644 --- a/rust/sealevel/client/src/core.rs +++ b/rust/sealevel/client/src/core.rs @@ -1,22 +1,67 @@ +use borsh::BorshDeserialize; use hyperlane_sealevel_mailbox::protocol_fee::ProtocolFee; use serde::{Deserialize, Serialize}; use solana_program::pubkey::Pubkey; use solana_sdk::signature::Signer; +use solana_sdk::{compute_budget, compute_budget::ComputeBudgetInstruction}; use std::collections::HashMap; use std::{fs::File, path::Path}; -use crate::ONE_SOL_IN_LAMPORTS; use crate::{ artifacts::{read_json, write_json}, cmd_utils::{create_and_write_keypair, create_new_directory, deploy_program}, multisig_ism::deploy_multisig_ism_message_id, Context, CoreCmd, CoreDeploy, CoreSubCmd, }; +use crate::{DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT, ONE_SOL_IN_LAMPORTS}; use hyperlane_core::H256; use hyperlane_sealevel_igp::accounts::{SOL_DECIMALS, TOKEN_EXCHANGE_RATE_SCALE}; +pub(crate) fn adjust_gas_price_if_needed(chain_name: &str, ctx: &mut Context) { + if chain_name.eq("solana") { + let mut initial_instructions = ctx.initial_instructions.borrow_mut(); + const PROCESS_DESIRED_PRIORITIZATION_FEE_LAMPORTS_PER_TX: u64 = 50_000_000; + const MICRO_LAMPORT_FEE_PER_LIMIT: u64 = + // Convert to micro-lamports + (PROCESS_DESIRED_PRIORITIZATION_FEE_LAMPORTS_PER_TX * 1_000_000) + // Divide by the max compute units + / DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64; + + for i in initial_instructions.iter_mut() { + if i.instruction.program_id != compute_budget::id() { + continue; + } + if let Ok(compute_budget_instruction) = + ComputeBudgetInstruction::try_from_slice(&i.instruction.data) + { + if matches!( + compute_budget_instruction, + ComputeBudgetInstruction::SetComputeUnitPrice { .. } + ) { + // The compute unit price has already been set, so we override it and return early + i.instruction = ComputeBudgetInstruction::set_compute_unit_price( + MICRO_LAMPORT_FEE_PER_LIMIT, + ); + return; + } + } + } + + initial_instructions.push( + ( + ComputeBudgetInstruction::set_compute_unit_price(MICRO_LAMPORT_FEE_PER_LIMIT), + Some(format!( + "Set compute unit price to {}", + MICRO_LAMPORT_FEE_PER_LIMIT + )), + ) + .into(), + ); + } +} + #[derive(serde::Serialize, serde::Deserialize, PartialEq, Debug)] #[serde(rename_all = "camelCase")] struct ProtocolFeeConfig { @@ -39,6 +84,8 @@ impl Default for ProtocolFeeConfig { pub(crate) fn process_core_cmd(mut ctx: Context, cmd: CoreCmd) { match cmd.cmd { CoreSubCmd::Deploy(core) => { + adjust_gas_price_if_needed(core.chain.as_str(), &mut ctx); + let environments_dir = create_new_directory(&core.env_args.environments_dir, &core.env_args.environment); let chain_dir = create_new_directory(&environments_dir, &core.chain); @@ -50,9 +97,11 @@ pub(crate) fn process_core_cmd(mut ctx: Context, cmd: CoreCmd) { &core.built_so_dir, core.use_existing_keys, &key_dir, + core.local_domain, ); - let mailbox_program_id = deploy_mailbox(&mut ctx, &core, &key_dir, ism_program_id); + let mailbox_program_id = + deploy_mailbox(&mut ctx, &core, &key_dir, ism_program_id, core.local_domain); let validator_announce_program_id = deploy_validator_announce(&mut ctx, &core, &key_dir, mailbox_program_id); @@ -78,6 +127,7 @@ fn deploy_mailbox( core: &CoreDeploy, key_dir: &Path, default_ism: Pubkey, + local_domain: u32, ) -> Pubkey { let (keypair, keypair_path) = create_and_write_keypair( key_dir, @@ -94,6 +144,7 @@ fn deploy_mailbox( .to_str() .unwrap(), &ctx.client.url(), + local_domain, ); println!("Deployed Mailbox at program ID {}", program_id); @@ -152,6 +203,7 @@ fn deploy_validator_announce( .to_str() .unwrap(), &ctx.client.url(), + core.local_domain, ); println!("Deployed ValidatorAnnounce at program ID {}", program_id); @@ -237,6 +289,7 @@ fn deploy_igp(ctx: &mut Context, core: &CoreDeploy, key_dir: &Path) -> (Pubkey, .to_str() .unwrap(), &ctx.client.url(), + core.local_domain, ); println!("Deployed IGP at program ID {}", program_id); diff --git a/rust/sealevel/client/src/igp.rs b/rust/sealevel/client/src/igp.rs index f41ddad2fb..0d4c5c0e9e 100644 --- a/rust/sealevel/client/src/igp.rs +++ b/rust/sealevel/client/src/igp.rs @@ -20,7 +20,7 @@ use solana_sdk::{ signature::{Keypair, Signer as _}, }; -use hyperlane_core::H256; +use hyperlane_core::{KnownHyperlaneDomain, H256}; use hyperlane_sealevel_igp::{ accounts::{ @@ -68,8 +68,14 @@ pub(crate) fn process_igp_cmd(mut ctx: Context, cmd: IgpCmd) { let ism_dir = create_new_directory(&environments_dir, "igp"); let chain_dir = create_new_directory(&ism_dir, &deploy.chain); let key_dir = create_new_directory(&chain_dir, "keys"); + let local_domain = deploy + .chain + .parse::() + .map(|v| v as u32) + .expect("Invalid chain name"); - let program_id = deploy_igp_program(&mut ctx, &deploy.built_so_dir, true, &key_dir); + let program_id = + deploy_igp_program(&mut ctx, &deploy.built_so_dir, true, &key_dir, local_domain); write_json::( &chain_dir.join("program-ids.json"), @@ -409,6 +415,7 @@ fn deploy_igp_program( built_so_dir: &Path, use_existing_keys: bool, key_dir: &Path, + local_domain: u32, ) -> Pubkey { let (keypair, keypair_path) = create_and_write_keypair( key_dir, @@ -425,6 +432,7 @@ fn deploy_igp_program( .to_str() .unwrap(), &ctx.client.url(), + local_domain, ); println!("Deployed IGP at program ID {}", program_id); diff --git a/rust/sealevel/client/src/main.rs b/rust/sealevel/client/src/main.rs index 34f0214ee7..8e9f3e5861 100644 --- a/rust/sealevel/client/src/main.rs +++ b/rust/sealevel/client/src/main.rs @@ -710,7 +710,7 @@ fn main() { payer_keypair.pubkey(), Some(PayerKeypair { keypair: payer_keypair, - keypair_path, + keypair_path: keypair_path.clone(), }), ) } else { @@ -724,6 +724,7 @@ fn main() { let commitment = CommitmentConfig::confirmed(); let mut instructions = vec![]; + if cli.compute_budget != DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT { assert!(cli.compute_budget <= MAX_COMPUTE_UNIT_LIMIT); instructions.push( diff --git a/rust/sealevel/client/src/multisig_ism.rs b/rust/sealevel/client/src/multisig_ism.rs index e354f39bb8..8911bedfc0 100644 --- a/rust/sealevel/client/src/multisig_ism.rs +++ b/rust/sealevel/client/src/multisig_ism.rs @@ -12,7 +12,7 @@ use crate::{ Context, MultisigIsmMessageIdCmd, MultisigIsmMessageIdSubCmd, }; -use hyperlane_core::H160; +use hyperlane_core::{KnownHyperlaneDomain, H160}; use hyperlane_sealevel_multisig_ism_message_id::{ access_control_pda_seeds, @@ -53,9 +53,19 @@ pub(crate) fn process_multisig_ism_message_id_cmd(mut ctx: Context, cmd: Multisi let chain_dir = create_new_directory(&ism_dir, &deploy.chain); let context_dir = create_new_directory(&chain_dir, &deploy.context); let key_dir = create_new_directory(&context_dir, "keys"); + let local_domain = deploy + .chain + .parse::() + .map(|v| v as u32) + .expect("Invalid chain name"); - let ism_program_id = - deploy_multisig_ism_message_id(&mut ctx, &deploy.built_so_dir, true, &key_dir); + let ism_program_id = deploy_multisig_ism_message_id( + &mut ctx, + &deploy.built_so_dir, + true, + &key_dir, + local_domain, + ); write_json::( &context_dir.join("program-ids.json"), @@ -158,6 +168,7 @@ pub(crate) fn deploy_multisig_ism_message_id( built_so_dir: &Path, use_existing_keys: bool, key_dir: &Path, + local_domain: u32, ) -> Pubkey { let (keypair, keypair_path) = create_and_write_keypair( key_dir, @@ -174,6 +185,7 @@ pub(crate) fn deploy_multisig_ism_message_id( .to_str() .unwrap(), &ctx.client.url(), + local_domain, ); println!( @@ -197,6 +209,10 @@ pub(crate) fn deploy_multisig_ism_message_id( ), ) .send_with_payer(); + println!( + "initialized Multisig ISM Message ID at program ID {}", + program_id + ); program_id } diff --git a/rust/sealevel/client/src/router.rs b/rust/sealevel/client/src/router.rs index ae20abc5bd..c2826f2405 100644 --- a/rust/sealevel/client/src/router.rs +++ b/rust/sealevel/client/src/router.rs @@ -15,9 +15,10 @@ use hyperlane_sealevel_connection_client::router::RemoteRouterConfig; use hyperlane_sealevel_igp::accounts::{Igp, InterchainGasPaymasterType, OverheadIgp}; use crate::{ + adjust_gas_price_if_needed, artifacts::{write_json, HexAndBase58ProgramIdArtifact}, cmd_utils::{create_and_write_keypair, create_new_directory, deploy_program_idempotent}, - read_core_program_ids, Context, CoreProgramIds, + read_core_program_ids, warp_route, Context, CoreProgramIds, }; /// Optional connection client configuration. @@ -185,6 +186,7 @@ pub(crate) trait RouterDeployer: .to_str() .unwrap(), &chain_config.rpc_urls[0].http, + chain_config.domain_id(), ) .unwrap(); @@ -348,6 +350,8 @@ pub(crate) fn deploy_routers< .filter(|(_, app_config)| app_config.router_config().foreign_deployment.is_none()) .collect::>(); + warp_route::install_spl_token_cli(); + // Now we deploy to chains that don't have a foreign deployment for (chain_name, app_config) in app_configs_to_deploy.iter() { let chain_config = chain_configs @@ -360,6 +364,8 @@ pub(crate) fn deploy_routers< } } + adjust_gas_price_if_needed(chain_name.as_str(), ctx); + // Deploy - this is idempotent. let program_id = deployer.deploy( ctx, @@ -537,6 +543,8 @@ fn enroll_all_remote_routers< routers: &HashMap, ) { for (chain_name, _) in app_configs_to_deploy.iter() { + adjust_gas_price_if_needed(chain_name.as_str(), ctx); + let chain_config = chain_configs .get(*chain_name) .unwrap_or_else(|| panic!("Chain config not found for chain: {}", chain_name)); diff --git a/rust/sealevel/client/src/warp_route.rs b/rust/sealevel/client/src/warp_route.rs index 28ef3766ca..e40adfa574 100644 --- a/rust/sealevel/client/src/warp_route.rs +++ b/rust/sealevel/client/src/warp_route.rs @@ -3,7 +3,11 @@ use hyperlane_core::H256; use hyperlane_sealevel_token_collateral::plugin::CollateralPlugin; use hyperlane_sealevel_token_native::plugin::NativePlugin; use serde::{Deserialize, Serialize}; -use std::{collections::HashMap, fmt::Debug}; +use std::{ + collections::HashMap, + fmt::Debug, + process::{Command, Stdio}, +}; use solana_client::{client_error::ClientError, rpc_client::RpcClient}; @@ -78,6 +82,7 @@ struct TokenMetadata { name: String, symbol: String, total_supply: Option, + uri: Option, } #[derive(Debug, Deserialize, Serialize, Clone)] @@ -243,6 +248,9 @@ impl RouterDeployer for WarpRouteDeployer { remote_decimals: app_config.decimal_metadata.remote_decimals(), }; + let home_path = std::env::var("HOME").unwrap(); + let spl_token_binary_path = format!("{home_path}/.cargo/bin/spl-token"); + match &app_config.token_type { TokenType::Native => ctx.new_txn().add( hyperlane_sealevel_token_native::instruction::init_instruction( @@ -255,23 +263,51 @@ impl RouterDeployer for WarpRouteDeployer { TokenType::Synthetic(_token_metadata) => { let decimals = init.decimals; - let init_txn = ctx.new_txn().add( - hyperlane_sealevel_token::instruction::init_instruction( - program_id, - ctx.payer_pubkey, - init, + ctx.new_txn() + .add( + hyperlane_sealevel_token::instruction::init_instruction( + program_id, + ctx.payer_pubkey, + init, + ) + .unwrap(), ) - .unwrap(), - ); + .with_client(client) + .send_with_payer(); let (mint_account, _mint_bump) = Pubkey::find_program_address(hyperlane_token_mint_pda_seeds!(), &program_id); - // TODO: Also set Metaplex metadata? - init_txn.add( + + let mut cmd = Command::new(spl_token_binary_path.clone()); + cmd.args([ + "create-token", + mint_account.to_string().as_str(), + "--enable-metadata", + "-p", + spl_token_2022::id().to_string().as_str(), + "--url", + client.url().as_str(), + "--with-compute-unit-limit", + "500000", + "--mint-authority", + &ctx.payer_pubkey.to_string(), + "--fee-payer", + ctx.payer_keypair_path(), + ]); + println!("running command: {:?}", cmd); + let status = cmd + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .status() + .expect("Failed to run command"); + + println!("initialized metadata pointer. Status: {status}"); + + ctx.new_txn().add( spl_token_2022::instruction::initialize_mint2( &spl_token_2022::id(), &mint_account, - &mint_account, + &ctx.payer_pubkey, None, decimals, ) @@ -295,6 +331,71 @@ impl RouterDeployer for WarpRouteDeployer { } .with_client(client) .send_with_payer(); + + if let TokenType::Synthetic(token_metadata) = &app_config.token_type { + let (mint_account, _mint_bump) = + Pubkey::find_program_address(hyperlane_token_mint_pda_seeds!(), &program_id); + + let mut cmd = Command::new(spl_token_binary_path.clone()); + cmd.args([ + "initialize-metadata", + mint_account.to_string().as_str(), + token_metadata.name.as_str(), + token_metadata.symbol.as_str(), + token_metadata.uri.as_deref().unwrap_or(""), + "-p", + spl_token_2022::id().to_string().as_str(), + "--url", + client.url().as_str(), + "--with-compute-unit-limit", + "500000", + "--mint-authority", + ctx.payer_keypair_path(), + "--fee-payer", + ctx.payer_keypair_path(), + "--update-authority", + &ctx.payer_pubkey.to_string(), + ]); + println!("running command: {:?}", cmd); + let status = cmd + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .status() + .expect("Failed to run command"); + println!("initialized metadata. Status: {status}"); + + // Burn the metadata pointer, metadata, and mint authorities by moving them to the mint + let authorities_to_transfer = &["metadata-pointer", "metadata", "mint"]; + + for authority in authorities_to_transfer { + println!("Transferring authority: {authority} to the mint account {mint_account}"); + + let mut cmd = Command::new(spl_token_binary_path.clone()); + cmd.args([ + "authorize", + mint_account.to_string().as_str(), + authority, + mint_account.to_string().as_str(), + "-p", + spl_token_2022::id().to_string().as_str(), + "--url", + client.url().as_str(), + "--with-compute-unit-limit", + "500000", + "--fee-payer", + ctx.payer_keypair_path(), + "--authority", + ctx.payer_keypair_path(), + ]); + println!("Running command: {:?}", cmd); + let status = cmd + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .status() + .expect("Failed to run command"); + println!("Set the {authority} authority to the mint account. Status: {status}"); + } + } } /// Sets gas router configs on all deployable chains. @@ -539,3 +640,30 @@ pub fn parse_token_account_data(token_type: FlatTokenType, data: &mut &[u8]) { } } } + +pub fn install_spl_token_cli() { + println!("Installing cargo 1.76.0 (required by spl-token-cli)"); + Command::new("rustup") + .args(["toolchain", "install", "1.76.0"]) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .status() + .expect("Failed to run command"); + println!("Installing the spl token cli"); + Command::new("cargo") + .args([ + "+1.76.0", + "install", + "spl-token-cli", + "--git", + "https://github.com/hyperlane-xyz/solana-program-library", + "--branch", + "dan/create-token-for-mint", + "--rev", + "c1278a3f1", + ]) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .status() + .expect("Failed to run command"); +} diff --git a/rust/sealevel/environments/mainnet3/multisig-ism-message-id/solana/hyperlane/program-ids.json b/rust/sealevel/environments/mainnet3/multisig-ism-message-id/solana/hyperlane/program-ids.json new file mode 100644 index 0000000000..afe14eae61 --- /dev/null +++ b/rust/sealevel/environments/mainnet3/multisig-ism-message-id/solana/hyperlane/program-ids.json @@ -0,0 +1,3 @@ +{ + "program_id": "HDXcKGggF4aLRE1Q6ApF2rLwriSQjpjrnE811cwH21mr" +} \ No newline at end of file diff --git a/rust/sealevel/environments/mainnet3/solana/core/program-ids.json b/rust/sealevel/environments/mainnet3/solana/core/program-ids.json index 0598f8dbc2..daaab86c92 100644 --- a/rust/sealevel/environments/mainnet3/solana/core/program-ids.json +++ b/rust/sealevel/environments/mainnet3/solana/core/program-ids.json @@ -1,7 +1,7 @@ { "mailbox": "4rRZgaC1DaCqtWYLzg14ftuXKPuHe1nGEM6ZtNpim3Wz", "validator_announce": "bn63TQYrzzK9H3XQwZ1gzGdNS91xkt5YaTinFPWyahR", - "multisig_ism_message_id": "2abS919HMBQ39GviSgjMQQf5L1G7cfdbAiNq61wQ8CvJ", + "multisig_ism_message_id": "HDXcKGggF4aLRE1Q6ApF2rLwriSQjpjrnE811cwH21mr", "igp_program_id": "8cPZ6Lv49h1cYBb6q29E2pxn4xbWjJGxfarNE8LqP1Ks", "overhead_igp_account": "5FG1TUuhXGKdMbbH8uHEnUghazD4aVfEPAgKLNGNx3SL", "igp_account": "HkqbGqRX7Fi5pwqi8HkDaUhHK6mGWsy7Rt17fpgBrbP5" diff --git a/rust/sealevel/environments/mainnet3/warp-routes/eclipsesol/program-ids.json b/rust/sealevel/environments/mainnet3/warp-routes/eclipsesol/program-ids.json new file mode 100644 index 0000000000..807bb7f3b5 --- /dev/null +++ b/rust/sealevel/environments/mainnet3/warp-routes/eclipsesol/program-ids.json @@ -0,0 +1,10 @@ +{ + "eclipse": { + "hex": "0x6d8f3a8e68449b4c5fa5f09f9f9bf82607f6b2b1b6052260bf7c5c66aa5a5684", + "base58": "8Ng7TLE223sWcRjH7rEdUPoR3seaugqPw1djdQRRZ6Gj" + }, + "solana": { + "hex": "0x6903def7c07b2844eb549e7037651c07f508884e0c962345c8bb2b9834633e15", + "base58": "84wESfpyisKVYwkpaJpkHS6XxkLPC5bLZrTD1jQw1TrL" + } +} \ No newline at end of file diff --git a/rust/sealevel/environments/mainnet3/warp-routes/eclipsesol/token-config.json b/rust/sealevel/environments/mainnet3/warp-routes/eclipsesol/token-config.json new file mode 100644 index 0000000000..993d253708 --- /dev/null +++ b/rust/sealevel/environments/mainnet3/warp-routes/eclipsesol/token-config.json @@ -0,0 +1,15 @@ +{ + "solana": { + "type": "native", + "decimals": 9, + "interchainGasPaymaster": "HkqbGqRX7Fi5pwqi8HkDaUhHK6mGWsy7Rt17fpgBrbP5" + }, + "eclipse": { + "type": "synthetic", + "decimals": 9, + "name": "Solana", + "symbol": "SOL", + "uri": "https://github.com/hyperlane-xyz/hyperlane-registry/blob/b661127dd3dce5ea98b78ae0051fbd10c384b173/deployments/warp_routes/SOL/eclipse/metadata.json", + "interchainGasPaymaster": "AgjedtgQKTWGR77ULJ9j9AhLjNDk1D3BTtuxKmcZrJqE" + } +} diff --git a/rust/sealevel/environments/mainnet3/warp-routes/pzeth/program-ids.json b/rust/sealevel/environments/mainnet3/warp-routes/pzeth/program-ids.json new file mode 100644 index 0000000000..e5776d7688 --- /dev/null +++ b/rust/sealevel/environments/mainnet3/warp-routes/pzeth/program-ids.json @@ -0,0 +1,10 @@ +{ + "solana": { + "hex": "0xe9792265ec273ffc17731af890d3e9963e9d744e7b99f02491911ce1bb75b8cb", + "base58": "GiP8GwN1GsscVJvmKSD4muDEihRzZRa9mxnS1Toi64pa" + }, + "ethereum": { + "hex": "0x0000000000000000000000001d622da2ce4c4d9d4b0611718cb3bcdcad008dd4", + "base58": "111111111111Qk6NeeudaXPNPB2XfB1yQ9furMh" + } +} \ No newline at end of file diff --git a/rust/sealevel/environments/mainnet3/warp-routes/pzeth/token-config.json b/rust/sealevel/environments/mainnet3/warp-routes/pzeth/token-config.json new file mode 100644 index 0000000000..43890fc005 --- /dev/null +++ b/rust/sealevel/environments/mainnet3/warp-routes/pzeth/token-config.json @@ -0,0 +1,18 @@ +{ + "solana": { + "type": "synthetic", + "decimals": 9, + "name": "Renzo Restaked LST", + "symbol": "pzETH", + "uri": "https://raw.githubusercontent.com/hyperlane-xyz/hyperlane-registry/12660fd34d30e960a748d87408a8d88f634f7454/deployments/warp_routes/pzETH/ethereum-solana-metadata.json", + "interchainGasPaymaster": "5FG1TUuhXGKdMbbH8uHEnUghazD4aVfEPAgKLNGNx3SL", + "remoteDecimals": 18 + }, + "ethereum": { + "type": "collateral", + "decimals": 18, + "token": "0x8c9532a60e0e7c6bbd2b2c1303f63ace1c3e9811", + "foreignDeployment": "0x1D622da2ce4C4D9D4B0611718cb3BcDcAd008DD4", + "destinationGas": 200000 + } +} \ No newline at end of file diff --git a/rust/sealevel/libraries/multisig-ism/src/test_data.rs b/rust/sealevel/libraries/multisig-ism/src/test_data.rs index 952bde14b9..75a7deeb8f 100644 --- a/rust/sealevel/libraries/multisig-ism/src/test_data.rs +++ b/rust/sealevel/libraries/multisig-ism/src/test_data.rs @@ -43,37 +43,40 @@ pub fn get_multisig_ism_test_data() -> MultisigIsmTestData { "0xcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", ) .unwrap(), - index: 69, + // Intentionally set to a different value than the message's nonce to test + // that the checkpoint in the ISM is constructed correctly using the metadata's + // merkle index. + index: message.nonce + 1, }, message_id: message.id(), }; // checkpoint.signing_hash() is equal to: - // 0x5c2b4cbafdd7319d9a3dea4aa078748ae53368e1521494a80cf4259b9c6dba6a + // 0x3fd308215a20af20b137372f8a69fd336ebf93d57d4076a7c46e13f315255257 // Validator 0: // Address: 0xE3DCDBbc248cE191bDc271f3FCcd0d95911BFC5D // Private Key: 0x788aa7213bd92ff92017d767fde0d75601425818c8e4b21e87314c2a4dcd6091 let validator_0 = H160::from_str("0xE3DCDBbc248cE191bDc271f3FCcd0d95911BFC5D").unwrap(); - // > await (new ethers.Wallet('0x788aa7213bd92ff92017d767fde0d75601425818c8e4b21e87314c2a4dcd6091')).signMessage(ethers.utils.arrayify('0x5c2b4cbafdd7319d9a3dea4aa078748ae53368e1521494a80cf4259b9c6dba6a')) - // '0xb8875fb75adf471e43a943b78eb422ffe86a4291fa6324f9f875241605ca831d2b4225358936deee7501cf305aafc1677e3dc9bcfea4caec54f4cde49d416bd91b' - let signature_0 = hex::decode("b8875fb75adf471e43a943b78eb422ffe86a4291fa6324f9f875241605ca831d2b4225358936deee7501cf305aafc1677e3dc9bcfea4caec54f4cde49d416bd91b").unwrap(); + // > await (new ethers.Wallet('0x788aa7213bd92ff92017d767fde0d75601425818c8e4b21e87314c2a4dcd6091')).signMessage(ethers.utils.arrayify('0x3fd308215a20af20b137372f8a69fd336ebf93d57d4076a7c46e13f315255257')) + // '0x081d398e1452ae12267f63f224d3037b4bb3f496cb55c14a2076c5e27ed944ad6d8e10d3164bc13b5820846a3f19e013e1c551b67a3c863882f7b951acdab96d1c' + let signature_0 = hex::decode("081d398e1452ae12267f63f224d3037b4bb3f496cb55c14a2076c5e27ed944ad6d8e10d3164bc13b5820846a3f19e013e1c551b67a3c863882f7b951acdab96d1c").unwrap(); // Validator 1: // Address: 0xb25206874C24733F05CC0dD11924724A8E7175bd // Private Key: 0x4a599de3915f404d84a2ebe522bfe7032ebb1ca76a65b55d6eb212b129043a0e let validator_1 = H160::from_str("0xb25206874C24733F05CC0dD11924724A8E7175bd").unwrap(); - // > await (new ethers.Wallet('0x4a599de3915f404d84a2ebe522bfe7032ebb1ca76a65b55d6eb212b129043a0e')).signMessage(ethers.utils.arrayify('0x5c2b4cbafdd7319d9a3dea4aa078748ae53368e1521494a80cf4259b9c6dba6a')) - // '0xfa8d61da2e0ac6f32c8432e75770d90483613efe96b442fbca9ca8d200447bf979f46529c7341879333a9c24a7d3fba08b53d13447618b71cf2ee4734e82f96e1c' - let signature_1 = hex::decode("fa8d61da2e0ac6f32c8432e75770d90483613efe96b442fbca9ca8d200447bf979f46529c7341879333a9c24a7d3fba08b53d13447618b71cf2ee4734e82f96e1c").unwrap(); + // > await (new ethers.Wallet('0x4a599de3915f404d84a2ebe522bfe7032ebb1ca76a65b55d6eb212b129043a0e')).signMessage(ethers.utils.arrayify('0x3fd308215a20af20b137372f8a69fd336ebf93d57d4076a7c46e13f315255257')) + // '0x0c189e25dea6bb93292af16fd0516f3adc8a19556714c0b8d624016175bebcba7a5fe8218dad6fc86faeb8104fad8390ccdec989d992e852553ea6b61fbb2eda1b' + let signature_1 = hex::decode("0c189e25dea6bb93292af16fd0516f3adc8a19556714c0b8d624016175bebcba7a5fe8218dad6fc86faeb8104fad8390ccdec989d992e852553ea6b61fbb2eda1b").unwrap(); // Validator 2: // Address: 0x28b8d0E2bBfeDe9071F8Ff3DaC9CcE3d3176DBd3 // Private Key: 0x2cc76d56db9924ddc3388164454dfea9edd2d5f5da81102fd3594fc7c5281515 let validator_2 = H160::from_str("0x28b8d0E2bBfeDe9071F8Ff3DaC9CcE3d3176DBd3").unwrap(); - // > await (new ethers.Wallet('0x2cc76d56db9924ddc3388164454dfea9edd2d5f5da81102fd3594fc7c5281515')).signMessage(ethers.utils.arrayify('0x5c2b4cbafdd7319d9a3dea4aa078748ae53368e1521494a80cf4259b9c6dba6a')) - // '0x6c60c3744f6bf779017b8ab9aa8eed60bf53c317cf4d74d765015cc0a7dcad783dee39334bfc7e4baab944355914e7431e4286df8c0557a0c1f6ba867677da421b' - let signature_2 = hex::decode("6c60c3744f6bf779017b8ab9aa8eed60bf53c317cf4d74d765015cc0a7dcad783dee39334bfc7e4baab944355914e7431e4286df8c0557a0c1f6ba867677da421b").unwrap(); + // > await (new ethers.Wallet('0x2cc76d56db9924ddc3388164454dfea9edd2d5f5da81102fd3594fc7c5281515')).signMessage(ethers.utils.arrayify('0x3fd308215a20af20b137372f8a69fd336ebf93d57d4076a7c46e13f315255257')) + // '0x5493449e8a09c1105195ecf913997de51bd50926a075ad98fe3e845e0a11126b5212a2cd1afdd35a44322146d31f8fa3d179d8a9822637d8db0e2fa8b3d292421b' + let signature_2 = hex::decode("5493449e8a09c1105195ecf913997de51bd50926a075ad98fe3e845e0a11126b5212a2cd1afdd35a44322146d31f8fa3d179d8a9822637d8db0e2fa8b3d292421b").unwrap(); MultisigIsmTestData { message, diff --git a/rust/sealevel/programs/hyperlane-sealevel-token/src/plugin.rs b/rust/sealevel/programs/hyperlane-sealevel-token/src/plugin.rs index c5029f9423..7928ec8a51 100644 --- a/rust/sealevel/programs/hyperlane-sealevel-token/src/plugin.rs +++ b/rust/sealevel/programs/hyperlane-sealevel-token/src/plugin.rs @@ -8,12 +8,13 @@ use hyperlane_sealevel_token_lib::{ accounts::HyperlaneToken, message::TokenMessage, processor::HyperlaneSealevelTokenPlugin, }; use serializable_account_meta::SerializableAccountMeta; +#[cfg(not(target_arch = "sbf"))] +use solana_program::program_pack::Pack as _; use solana_program::{ account_info::{next_account_info, AccountInfo}, instruction::AccountMeta, program::{invoke, invoke_signed}, program_error::ProgramError, - program_pack::Pack as _, pubkey::Pubkey, rent::Rent, sysvar::Sysvar, @@ -74,6 +75,13 @@ impl SizedData for SyntheticPlugin { } impl SyntheticPlugin { + /// The size of the mint account. + // Need to hardcode this value because our `spl_token_2022` version doesn't include it. + // It was calculated by calling `ExtensionType::try_calculate_account_len::(vec![ExtensionType::MetadataPointer]).unwrap()` + #[cfg(target_arch = "sbf")] + const MINT_ACCOUNT_SIZE: usize = 234; + /// The size of the mint account. + #[cfg(not(target_arch = "sbf"))] const MINT_ACCOUNT_SIZE: usize = spl_token_2022::state::Mint::LEN; /// Returns Ok(()) if the mint account info is valid. diff --git a/rust/sealevel/programs/ism/multisig-ism-message-id/src/processor.rs b/rust/sealevel/programs/ism/multisig-ism-message-id/src/processor.rs index c479b69a94..5290d3d2c3 100644 --- a/rust/sealevel/programs/ism/multisig-ism-message-id/src/processor.rs +++ b/rust/sealevel/programs/ism/multisig-ism-message-id/src/processor.rs @@ -249,7 +249,7 @@ fn verify( merkle_tree_hook_address: metadata.origin_merkle_tree_hook, mailbox_domain: message.origin, root: metadata.merkle_root, - index: message.nonce, + index: metadata.merkle_index, }, message_id: message.id(), }, diff --git a/rust/utils/run-locally/src/solana.rs b/rust/utils/run-locally/src/solana.rs index 2ce54a5574..0ccca4f647 100644 --- a/rust/utils/run-locally/src/solana.rs +++ b/rust/utils/run-locally/src/solana.rs @@ -16,7 +16,7 @@ use crate::AGENT_BIN_PATH; /// The Solana CLI tool version to download and use. const SOLANA_CLI_VERSION: &str = "1.14.20"; const SOLANA_PROGRAM_LIBRARY_ARCHIVE: &str = - "https://github.com/hyperlane-xyz/solana-program-library/releases/download/2023-07-27-01/spl.tar.gz"; + "https://github.com/hyperlane-xyz/solana-program-library/releases/download/2024-08-23/spl.tar.gz"; // Solana program tuples of: // 0: Solana address or keypair for the bpf program diff --git a/typescript/infra/config/environments/mainnet3/gasPrices.json b/typescript/infra/config/environments/mainnet3/gasPrices.json index ea5a0fde60..a3e9489713 100644 --- a/typescript/infra/config/environments/mainnet3/gasPrices.json +++ b/typescript/infra/config/environments/mainnet3/gasPrices.json @@ -164,8 +164,8 @@ "decimals": 9 }, "solana": { - "amount": "0.001", - "decimals": 9 + "amount": "0.5", + "decimals": 1 }, "taiko": { "amount": "0.050000001", diff --git a/typescript/infra/config/environments/mainnet3/warp/addresses.json b/typescript/infra/config/environments/mainnet3/warp/addresses.json index feb5836384..6325ed7c8a 100644 --- a/typescript/infra/config/environments/mainnet3/warp/addresses.json +++ b/typescript/infra/config/environments/mainnet3/warp/addresses.json @@ -1,11 +1,8 @@ { - "ancient8": { - "HypERC20": "0x97423A68BAe94b5De52d767a17aBCc54c157c0E5", - "synthetic": "0x97423A68BAe94b5De52d767a17aBCc54c157c0E5" - }, "ethereum": { - "HypERC20Collateral": "0x8b4192B9Ad1fCa440A5808641261e5289e6de95D", - "collateral": "0x8b4192B9Ad1fCa440A5808641261e5289e6de95D", - "proxyAdmin": "0x7d0c8b23c5b35091972023ccc689cfedcd881c7d" + "HypERC20Collateral": "0x1D622da2ce4C4D9D4B0611718cb3BcDcAd008DD4", + "collateral": "0x1D622da2ce4C4D9D4B0611718cb3BcDcAd008DD4", + "proxyAdmin": "0xe41a3270875f28A03312877cD95A01e9a53664b1", + "timelockController": "0x0000000000000000000000000000000000000000" } } diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumSolanaPzETHWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumSolanaPzETHWarpConfig.ts new file mode 100644 index 0000000000..83b937b6e8 --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumSolanaPzETHWarpConfig.ts @@ -0,0 +1,34 @@ +import { ethers } from 'ethers'; + +import { + ChainMap, + RouterConfig, + TokenRouterConfig, + TokenType, +} from '@hyperlane-xyz/sdk'; + +import { DEPLOYER } from '../../owners.js'; + +export const getEthereumSolanaPzETHWarpConfig = async ( + routerConfig: ChainMap, +): Promise> => { + // @ts-ignore - foreignDeployment configs don't conform to the TokenRouterConfig + const solana: TokenRouterConfig = { + type: TokenType.synthetic, + foreignDeployment: 'GiP8GwN1GsscVJvmKSD4muDEihRzZRa9mxnS1Toi64pa', + gas: 300_000, + }; + + const ethereum: TokenRouterConfig = { + ...routerConfig.ethereum, + type: TokenType.collateral, + interchainSecurityModule: ethers.constants.AddressZero, + token: '0x8c9532a60e0e7c6bbd2b2c1303f63ace1c3e9811', + owner: DEPLOYER, + }; + + return { + solana, + ethereum, + }; +}; diff --git a/typescript/infra/config/environments/mainnet3/warp/verification.json b/typescript/infra/config/environments/mainnet3/warp/verification.json index 48f75ae1d8..4003b89cda 100644 --- a/typescript/infra/config/environments/mainnet3/warp/verification.json +++ b/typescript/infra/config/environments/mainnet3/warp/verification.json @@ -111,6 +111,25 @@ "constructorArguments": "0000000000000000000000007d0c8b23c5b35091972023ccc689cfedcd881c7d000000000000000000000000ed96482bea3c51a33b4c1ada8b438e33a236741300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b00000000000000000000000019b2cf952b70b217c90fc408714fbc1acd29a6a8000000000000000000000000d17b4100cc66a2f1b9a452007ff26365aaeb7ec3000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "name": "TransparentUpgradeableProxy" + }, + { + "address": "0xe41a3270875f28A03312877cD95A01e9a53664b1", + "constructorArguments": "", + "isProxy": false, + "name": "ProxyAdmin" + }, + { + "address": "0xbE6501A4E68a3463A217eC0dEc862b1593C0A47D", + "constructorArguments": "0000000000000000000000008c9532a60e0e7c6bbd2b2c1303f63ace1c3e9811000000000000000000000000c005dc82818d67af737725bd4bf75435d065d239", + "isProxy": false, + "name": "HypERC20Collateral" + }, + { + "address": "0x1D622da2ce4C4D9D4B0611718cb3BcDcAd008DD4", + "constructorArguments": "000000000000000000000000be6501a4e68a3463a217ec0dec862b1593c0a47d000000000000000000000000e41a3270875f28a03312877cd95a01e9a53664b100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "expectedimplementation": "0xbE6501A4E68a3463A217eC0dEc862b1593C0A47D", + "isProxy": true, + "name": "TransparentUpgradeableProxy" } ], "inevm": [ diff --git a/typescript/infra/config/warp.ts b/typescript/infra/config/warp.ts index 8f2ea00837..b5ae6f8a0d 100644 --- a/typescript/infra/config/warp.ts +++ b/typescript/infra/config/warp.ts @@ -13,6 +13,7 @@ import { getArbitrumNeutronEclipWarpConfig } from './environments/mainnet3/warp/ import { getArbitrumNeutronTiaWarpConfig } from './environments/mainnet3/warp/configGetters/getArbitrumNeutronTiaWarpConfig.js'; import { getEthereumInevmUSDCWarpConfig } from './environments/mainnet3/warp/configGetters/getEthereumInevmUSDCWarpConfig.js'; import { getEthereumInevmUSDTWarpConfig } from './environments/mainnet3/warp/configGetters/getEthereumInevmUSDTWarpConfig.js'; +import { getEthereumSolanaPzETHWarpConfig } from './environments/mainnet3/warp/configGetters/getEthereumSolanaPzETHWarpConfig.js'; import { getEthereumVictionETHWarpConfig } from './environments/mainnet3/warp/configGetters/getEthereumVictionETHWarpConfig.js'; import { getEthereumVictionUSDCWarpConfig } from './environments/mainnet3/warp/configGetters/getEthereumVictionUSDCWarpConfig.js'; import { getEthereumVictionUSDTWarpConfig } from './environments/mainnet3/warp/configGetters/getEthereumVictionUSDTWarpConfig.js'; @@ -27,6 +28,7 @@ export enum WarpRouteIds { ArbitrumNeutronTIA = 'TIA/arbitrum-neutron', EthereumInevmUSDC = 'USDC/ethereum-inevm', EthereumInevmUSDT = 'USDT/ethereum-inevm', + EthereumSolanaPzETH = 'pzETH/ethereum-solana', EthereumVictionETH = 'ETH/ethereum-viction', EthereumVictionUSDC = 'USDC/ethereum-viction', EthereumVictionUSDT = 'USDT/ethereum-viction', @@ -56,6 +58,7 @@ export const warpConfigGetterMap: Record< [WarpRouteIds.EthereumVictionUSDC]: getEthereumVictionUSDCWarpConfig, [WarpRouteIds.EthereumVictionUSDT]: getEthereumVictionUSDTWarpConfig, [WarpRouteIds.MantapacificNeutronTIA]: getMantapacificNeutronTiaWarpConfig, + [WarpRouteIds.EthereumSolanaPzETH]: getEthereumSolanaPzETHWarpConfig, }; export async function getWarpConfig( diff --git a/typescript/sdk/src/providers/transactionFeeEstimators.ts b/typescript/sdk/src/providers/transactionFeeEstimators.ts index 54368bd7f5..a2e1cb1bb2 100644 --- a/typescript/sdk/src/providers/transactionFeeEstimators.ts +++ b/typescript/sdk/src/providers/transactionFeeEstimators.ts @@ -126,7 +126,7 @@ export async function estimateTransactionFeeSolanaWeb3({ const { value } = await connection.simulateTransaction( transaction.transaction, ); - assert(!value.err, `Solana gas estimation failed: ${value.err}`); + assert(!value.err, `Solana gas estimation failed: ${JSON.stringify(value)}`); const gasUnits = BigInt(value.unitsConsumed || 0); const recentFees = await connection.getRecentPrioritizationFees(); const gasPrice = BigInt(recentFees[0].prioritizationFee); diff --git a/typescript/sdk/src/router/GasRouterDeployer.ts b/typescript/sdk/src/router/GasRouterDeployer.ts index 2c929123a4..76aabb460a 100644 --- a/typescript/sdk/src/router/GasRouterDeployer.ts +++ b/typescript/sdk/src/router/GasRouterDeployer.ts @@ -40,7 +40,7 @@ export abstract class GasRouterDeployer< const remoteConfigs = remoteDomains .map((domain, i) => ({ domain, - gas: configMap[remoteChains[i]].gas ?? DEFAULT_GAS_OVERHEAD, + gas: configMap[remoteChains[i]]?.gas ?? DEFAULT_GAS_OVERHEAD, })) .filter(({ gas }, index) => !currentConfigs[index].eq(gas)); if (remoteConfigs.length == 0) {